Skip to content
Snippets Groups Projects
Unverified Commit be35a04c authored by Nemanja Glumac's avatar Nemanja Glumac Committed by GitHub
Browse files

Re-wire the `PersistedModels` entity to use RTK Query under the hood (#41686)

* Add persisted info types

* Keep exports sorted alphabetically

* Add boilerplate `perist` API

* Use RTK Query for the `PersistedModels.list`

* Throw for unused `PersistedModels` endpoints

* Update types

* Re-wire PersistedModels `get` requests to use RTK Query

* Fix the endpoint

* Refresh model cache using RTK Query

* Add cache invalidation

* Fix `PersistedModels.api.get` endpoint options

* Remove stray TODO comment

* Explain the difference between the `persisted-info` and the `persisted-model` tags

* Invalidate `persisted-info` list upon refreshing a single model
parent e9fd9a40
No related branches found
No related tags found
No related merge requests found
...@@ -18,11 +18,14 @@ export * from "./models"; ...@@ -18,11 +18,14 @@ export * from "./models";
export * from "./modelIndexes"; export * from "./modelIndexes";
export * from "./notifications"; export * from "./notifications";
export * from "./pagination"; export * from "./pagination";
export * from "./permissions"; export * from "./parameters";
export * from "./performance"; export * from "./performance";
export * from "./permissions";
export * from "./persist";
export * from "./query"; export * from "./query";
export * from "./revision"; export * from "./revision";
export * from "./schema"; export * from "./schema";
export * from "./search";
export * from "./segment"; export * from "./segment";
export * from "./session"; export * from "./session";
export * from "./settings"; export * from "./settings";
...@@ -35,8 +38,6 @@ export * from "./task"; ...@@ -35,8 +38,6 @@ export * from "./task";
export * from "./timeline"; export * from "./timeline";
export * from "./user"; export * from "./user";
export * from "./util"; export * from "./util";
export * from "./parameters";
export * from "./search";
export * from "./visualization-settings"; export * from "./visualization-settings";
// ISO8601 timestamp // ISO8601 timestamp
......
import type { ModelCacheRefreshStatus } from "./models";
import type { PaginationRequest, PaginationResponse } from "./pagination";
export type PersistedInfoId = number;
export type ListPersistedInfoRequest = PaginationRequest;
export type ListPersistedInfoResponse = {
data: ModelCacheRefreshStatus[];
} & PaginationResponse;
export type PersistedInfoRefreshSchedule = {
cron: string;
};
...@@ -75,6 +75,18 @@ export const cardApi = Api.injectEndpoints({ ...@@ -75,6 +75,18 @@ export const cardApi = Api.injectEndpoints({
}), }),
invalidatesTags: (_, error) => invalidateTags(error, [listTag("card")]), invalidatesTags: (_, error) => invalidateTags(error, [listTag("card")]),
}), }),
refreshModelCache: builder.mutation<void, CardId>({
query: id => ({
method: "POST",
url: `/api/card/${id}/refresh`,
}),
invalidatesTags: (_, error, id) =>
invalidateTags(error, [
idTag("card", id),
idTag("persisted-model", id),
listTag("persisted-info"),
]),
}),
}), }),
}); });
...@@ -85,4 +97,5 @@ export const { ...@@ -85,4 +97,5 @@ export const {
useUpdateCardMutation, useUpdateCardMutation,
useDeleteCardMutation, useDeleteCardMutation,
useCopyCardMutation, useCopyCardMutation,
useRefreshModelCacheMutation,
} = cardApi; } = cardApi;
...@@ -3,16 +3,17 @@ export * from "./alert"; ...@@ -3,16 +3,17 @@ export * from "./alert";
export * from "./api"; export * from "./api";
export * from "./api-key"; export * from "./api-key";
export * from "./automagic-dashboards"; export * from "./automagic-dashboards";
export * from "./bookmark";
export * from "./card"; export * from "./card";
export * from "./collection"; export * from "./collection";
export * from "./database";
export * from "./dashboard"; export * from "./dashboard";
export * from "./bookmark"; export * from "./database";
export * from "./dataset"; export * from "./dataset";
export * from "./field"; export * from "./field";
export * from "./login-history"; export * from "./login-history";
export * from "./metric"; export * from "./metric";
export * from "./permission"; export * from "./permission";
export * from "./persist";
export * from "./revision"; export * from "./revision";
export * from "./search"; export * from "./search";
export * from "./segment"; export * from "./segment";
......
import type {
ListPersistedInfoResponse,
CardId,
ListPersistedInfoRequest,
ModelCacheRefreshStatus,
PersistedInfoId,
PersistedInfoRefreshSchedule,
} from "metabase-types/api";
import { Api } from "./api";
import {
invalidateTags,
listTag,
providePersistedInfoListTags,
providePersistedInfoTags,
providePersistedModelTags,
} from "./tags";
export const persistApi = Api.injectEndpoints({
endpoints: builder => ({
listPersistedInfo: builder.query<
ListPersistedInfoResponse,
ListPersistedInfoRequest | void
>({
query: params => ({
method: "GET",
url: "/api/persist",
params,
}),
providesTags: response =>
response ? providePersistedInfoListTags(response.data) : [],
}),
getPersistedInfo: builder.query<ModelCacheRefreshStatus, PersistedInfoId>({
query: id => ({
method: "GET",
url: `/api/persist/${id}`,
}),
providesTags: model => (model ? providePersistedInfoTags(model) : []),
}),
getPersistedInfoByCard: builder.query<ModelCacheRefreshStatus, CardId>({
query: id => ({
method: "GET",
url: `/api/persist/card/${id}`,
}),
providesTags: model => (model ? providePersistedModelTags(model) : []),
}),
enablePersist: builder.mutation<void, void>({
query: () => ({
method: "POST",
url: "/api/persist/enable",
}),
invalidatesTags: (_, error) =>
invalidateTags(error, [listTag("persisted-info")]),
}),
disablePersist: builder.mutation<void, void>({
query: () => ({
method: "POST",
url: "/api/persist/disable",
}),
invalidatesTags: (_, error) =>
invalidateTags(error, [listTag("persisted-info")]),
}),
setRefreshSchedule: builder.mutation<void, PersistedInfoRefreshSchedule>({
query: body => ({
method: "POST",
url: "/api/persist/set-refresh/schedule",
body,
}),
invalidatesTags: (_, error) =>
invalidateTags(error, [listTag("persisted-info")]),
}),
}),
});
export const {
useListPersistedInfoQuery,
useGetPersistedInfoQuery,
useGetPersistedInfoByCardQuery,
useEnablePersistMutation,
useDisablePersistMutation,
useSetRefreshScheduleMutation,
} = persistApi;
...@@ -14,6 +14,8 @@ export const TAG_TYPES = [ ...@@ -14,6 +14,8 @@ export const TAG_TYPES = [
"indexed-entity", "indexed-entity",
"metric", "metric",
"permissions-group", "permissions-group",
"persisted-info",
"persisted-model",
"revision", "revision",
"schema", "schema",
"snippet", "snippet",
......
...@@ -20,6 +20,7 @@ import type { ...@@ -20,6 +20,7 @@ import type {
ListDashboardsResponse, ListDashboardsResponse,
Metric, Metric,
NativeQuerySnippet, NativeQuerySnippet,
ModelCacheRefreshStatus,
PopularItem, PopularItem,
RecentItem, RecentItem,
Revision, Revision,
...@@ -289,6 +290,33 @@ export function providePermissionsGroupTags( ...@@ -289,6 +290,33 @@ export function providePermissionsGroupTags(
return [idTag("permissions-group", group.id)]; return [idTag("permissions-group", group.id)];
} }
export function providePersistedInfoListTags(
statuses: ModelCacheRefreshStatus[],
): TagDescription<TagType>[] {
return [
listTag("persisted-info"),
...statuses.flatMap(providePersistedInfoTags),
];
}
export function providePersistedInfoTags(
status: ModelCacheRefreshStatus,
): TagDescription<TagType>[] {
return [idTag("persisted-info", status.id)];
}
/**
* We have to differentiate between the `persisted-info` and `persisted-model` tags
* because the model cache refresh lives on the card api `/api/card/model/:id/refresh`.
* That endpoint doesn't have information about the persisted info id, so we have to
* map the model id to the `card_id` on the ModelCacheRefreshStatus.
*/
export function providePersistedModelTags(
status: ModelCacheRefreshStatus,
): TagDescription<TagType>[] {
return [idTag("persisted-model", status.card_id)];
}
export function provideRevisionListTags( export function provideRevisionListTags(
revisions: Revision[], revisions: Revision[],
): TagDescription<TagType>[] { ): TagDescription<TagType>[] {
......
import { createSelector } from "@reduxjs/toolkit"; import { createSelector } from "@reduxjs/toolkit";
import { createEntity } from "metabase/lib/entities"; import { cardApi, persistApi } from "metabase/api";
import { createEntity, entityCompatibleQuery } from "metabase/lib/entities";
import { PersistedModelSchema } from "metabase/schema"; import { PersistedModelSchema } from "metabase/schema";
import { CardApi, PersistedModelsApi } from "metabase/services";
const REFRESH_CACHE = "metabase/entities/persistedModels/REFRESH_CACHE"; const REFRESH_CACHE = "metabase/entities/persistedModels/REFRESH_CACHE";
...@@ -22,17 +22,45 @@ const PersistedModels = createEntity({ ...@@ -22,17 +22,45 @@ const PersistedModels = createEntity({
schema: PersistedModelSchema, schema: PersistedModelSchema,
api: { api: {
get: ({ id, type }, ...args) => { get: ({ id, type }, options, dispatch) => {
return type === "byModelId" return type === "byModelId"
? PersistedModelsApi.getForModel({ id }, ...args) ? entityCompatibleQuery(
: PersistedModelSchema.get({ id }, ...args); id,
dispatch,
persistApi.endpoints.getPersistedInfoByCard,
)
: entityCompatibleQuery(
id,
dispatch,
persistApi.endpoints.getPersistedInfo,
);
},
list: (entityQuery, dispatch) =>
entityCompatibleQuery(
entityQuery,
dispatch,
persistApi.endpoints.listPersistedInfo,
),
create: () => {
throw new TypeError("PersistedModels.api.create is not supported");
},
update: () => {
throw new TypeError("PersistedModels.api.update is not supported");
},
delete: () => {
throw new TypeError("PersistedModels.api.delete is not supported");
}, },
}, },
objectActions: { objectActions: {
refreshCache: async job => { refreshCache: job => async dispatch => {
await CardApi.refreshModelCache({ id: job.card_id }); await entityCompatibleQuery(
return { type: REFRESH_CACHE, payload: job }; job.card_id,
dispatch,
cardApi.endpoints.refreshModelCache,
);
dispatch({ type: REFRESH_CACHE, payload: job });
}, },
}, },
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment