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

Migrate alerts to RTK (#41190)


* Replace AlertApi

* Replace AlertApi

* Replace AlertApi

* Fix bugs

* Fix bugs

* Fix typo

---------

Co-authored-by: default avatarKamil Mielnik <kamil@kamilmielnik.com>
parent 4b97c427
No related branches found
No related tags found
No related merge requests found
import type { Card } from "./card";
import type { CardId } from "./card";
import type { CollectionId } from "./collection";
import type { DashboardId } from "./dashboard";
import type { DashboardId, DashCardId } from "./dashboard";
import type { Channel } from "./notifications";
import type { Parameter } from "./parameters";
import type { UserId, UserInfo } from "./user";
export type AlertId = number;
export type AlertCondition = "goal" | "rows";
export interface Alert {
id: number;
id: AlertId;
name: string | null;
alert_above_goal: boolean | null;
alert_condition: "goal" | "rows";
alert_condition: AlertCondition;
alert_first_only: boolean;
skip_if_empty: boolean;
card: Card;
card: AlertCard;
parameters: Parameter[];
channels: Channel[];
......@@ -33,3 +36,38 @@ export interface Alert {
created_at: string;
updated_at: string;
}
export interface AlertCard {
id: CardId;
include_csv: boolean;
include_xls: boolean;
format_rows?: boolean;
dashboard_card_id?: DashCardId;
}
export interface ListAlertsRequest {
user_id?: UserId;
archived?: boolean;
}
export interface ListCardAlertsRequest {
id: CardId;
archived?: boolean;
}
export interface CreateAlertRequest {
card: AlertCard;
alert_condition: AlertCondition;
alert_first_only: boolean;
alert_above_goal: boolean;
channels: Channel[];
}
export interface UpdateAlertRequest {
id: AlertId;
card?: AlertCard;
alert_condition?: AlertCondition;
alert_first_only?: boolean;
alert_above_goal?: boolean;
channels?: Channel[];
}
import type { Alert } from "../alert";
import type { Alert, AlertCard } from "../alert";
import { createMockCard } from "./card";
import { createMockUserInfo } from "./user";
export const createMockAlert = (opts?: Partial<Alert>): Alert => ({
......@@ -11,7 +10,7 @@ export const createMockAlert = (opts?: Partial<Alert>): Alert => ({
alert_first_only: false,
skip_if_empty: false,
card: createMockCard(),
card: createMockAlertCard(),
parameters: [],
channels: [],
......@@ -32,3 +31,12 @@ export const createMockAlert = (opts?: Partial<Alert>): Alert => ({
...opts,
});
export function createMockAlertCard(opts?: Partial<AlertCard>): AlertCard {
return {
id: 1,
include_csv: false,
include_xls: false,
...opts,
};
}
......@@ -4,15 +4,17 @@ import { handleActions } from "redux-actions";
import { t } from "ttag";
import _ from "underscore";
import { alertApi } from "metabase/api";
import CS from "metabase/css/core/index.css";
import { entityCompatibleQuery } from "metabase/lib/entities";
import { RestfulRequest } from "metabase/lib/request";
import { addUndo } from "metabase/redux/undo";
import { AlertApi } from "metabase/services";
import { Icon } from "metabase/ui";
export const FETCH_ALL_ALERTS = "metabase/alerts/FETCH_ALL_ALERTS";
const fetchAllAlertsRequest = new RestfulRequest({
endpoint: AlertApi.list,
endpoint: (params, dispatch) =>
entityCompatibleQuery(params, dispatch, alertApi.endpoints.listAlerts),
actionPrefix: FETCH_ALL_ALERTS,
storeAsDictionary: true,
});
......@@ -28,7 +30,8 @@ export const FETCH_ALERTS_FOR_QUESTION_CLEAR_OLD_ALERTS =
export const FETCH_ALERTS_FOR_QUESTION =
"metabase/alerts/FETCH_ALERTS_FOR_QUESTION";
const fetchAlertsForQuestionRequest = new RestfulRequest({
endpoint: AlertApi.list_for_question,
endpoint: (params, dispatch) =>
entityCompatibleQuery(params, dispatch, alertApi.endpoints.listCardAlerts),
actionPrefix: FETCH_ALERTS_FOR_QUESTION,
storeAsDictionary: true,
});
......@@ -38,14 +41,15 @@ export const fetchAlertsForQuestion = questionId => {
payload: questionId,
type: FETCH_ALERTS_FOR_QUESTION_CLEAR_OLD_ALERTS,
});
await dispatch(fetchAlertsForQuestionRequest.trigger({ questionId }));
await dispatch(fetchAlertsForQuestionRequest.trigger({ id: questionId }));
dispatch({ type: FETCH_ALERTS_FOR_QUESTION });
};
};
export const CREATE_ALERT = "metabase/alerts/CREATE_ALERT";
const createAlertRequest = new RestfulRequest({
endpoint: AlertApi.create,
endpoint: (params, dispatch) =>
entityCompatibleQuery(params, dispatch, alertApi.endpoints.createAlert),
actionPrefix: CREATE_ALERT,
storeAsDictionary: true,
});
......@@ -92,7 +96,8 @@ function cleanAlert(alert) {
export const UPDATE_ALERT = "metabase/alerts/UPDATE_ALERT";
const updateAlertRequest = new RestfulRequest({
endpoint: AlertApi.update,
endpoint: (params, dispatch) =>
entityCompatibleQuery(params, dispatch, alertApi.endpoints.updateAlert),
actionPrefix: UPDATE_ALERT,
storeAsDictionary: true,
});
......@@ -123,13 +128,18 @@ export const UNSUBSCRIBE_FROM_ALERT = "metabase/alerts/UNSUBSCRIBE_FROM_ALERT";
export const UNSUBSCRIBE_FROM_ALERT_CLEANUP =
"metabase/alerts/UNSUBSCRIBE_FROM_ALERT_CLEANUP";
const unsubscribeFromAlertRequest = new RestfulRequest({
endpoint: AlertApi.unsubscribe,
endpoint: (params, dispatch) =>
entityCompatibleQuery(
params,
dispatch,
alertApi.endpoints.deleteAlertSubscription,
),
actionPrefix: UNSUBSCRIBE_FROM_ALERT,
storeAsDictionary: true,
});
export const unsubscribeFromAlert = alert => {
return async (dispatch, getState) => {
await dispatch(unsubscribeFromAlertRequest.trigger(alert));
await dispatch(unsubscribeFromAlertRequest.trigger(alert.id));
dispatch({ type: UNSUBSCRIBE_FROM_ALERT });
// This delay lets us to show "You're unsubscribed" text in place of an
......@@ -144,7 +154,8 @@ export const unsubscribeFromAlert = alert => {
export const DELETE_ALERT = "metabase/alerts/DELETE_ALERT";
const deleteAlertRequest = new RestfulRequest({
endpoint: AlertApi.update,
endpoint: (params, dispatch) =>
entityCompatibleQuery(params, dispatch, alertApi.endpoints.updateAlert),
actionPrefix: DELETE_ALERT,
storeAsDictionary: true,
});
......
import type {
Alert,
AlertId,
CreateAlertRequest,
ListAlertsRequest,
ListCardAlertsRequest,
UpdateAlertRequest,
} from "metabase-types/api";
import { Api } from "./api";
import {
idTag,
invalidateTags,
listTag,
provideAlertListTags,
provideAlertTags,
} from "./tags";
export const alertApi = Api.injectEndpoints({
endpoints: builder => ({
listAlerts: builder.query<Alert[], ListAlertsRequest | void>({
query: body => ({
method: "GET",
url: "/api/alert",
body,
}),
providesTags: (alerts = []) => provideAlertListTags(alerts),
}),
listCardAlerts: builder.query<Alert[], ListCardAlertsRequest>({
query: ({ id, ...body }) => ({
method: "GET",
url: `/api/alert/question/${id}`,
body,
}),
providesTags: (alerts = []) => provideAlertListTags(alerts),
}),
getAlert: builder.query<Alert, AlertId>({
query: id => ({
method: "GET",
url: `/api/alert/${id}`,
}),
providesTags: alert => (alert ? provideAlertTags(alert) : []),
}),
createAlert: builder.mutation<Alert, CreateAlertRequest>({
query: body => ({
method: "POST",
url: "/api/alert",
body,
}),
invalidatesTags: (alert, error) =>
invalidateTags(error, [listTag("alert")]),
}),
updateAlert: builder.mutation<Alert, UpdateAlertRequest>({
query: ({ id, ...body }) => ({
method: "PUT",
url: `/api/alert/${id}`,
body,
}),
invalidatesTags: (alert, error) =>
invalidateTags(error, [
listTag("alert"),
...(alert ? [idTag("alert", alert.id)] : []),
]),
}),
deleteAlertSubscription: builder.mutation<void, AlertId>({
query: id => ({
method: "DELETE",
url: `/api/alert/${id}/subscription`,
}),
invalidatesTags: (_, error, id) =>
invalidateTags(error, [listTag("alert"), idTag("alert", id)]),
}),
}),
});
export const {
useListAlertsQuery,
useListCardAlertsQuery,
useGetAlertQuery,
useCreateAlertMutation,
useUpdateAlertMutation,
useDeleteAlertSubscriptionMutation,
} = alertApi;
export * from "./activity";
export * from "./alert";
export * from "./api";
export * from "./api-key";
export * from "./automagic-dashboards";
......
......@@ -2,6 +2,7 @@ export type TagType = typeof TAG_TYPES[number];
export const TAG_TYPES = [
"action",
"alert",
"api-key",
"bookmark",
"card",
......
import type { TagDescription } from "@reduxjs/toolkit/query";
import type {
Alert,
ApiKey,
Bookmark,
Card,
......@@ -70,6 +71,19 @@ export function provideActivityItemTags(
return [idTag(TAG_TYPE_MAPPING[item.model], item.model_id)];
}
export function provideAlertListTags(
alerts: Alert[],
): TagDescription<TagType>[] {
return [listTag("alert"), ...alerts.flatMap(provideAlertTags)];
}
export function provideAlertTags(alert: Alert): TagDescription<TagType>[] {
return [
idTag("alert", alert.id),
...(alert.creator ? provideUserTags(alert.creator) : []),
];
}
export function provideApiKeyListTags(
apiKeys: ApiKey[],
): TagDescription<TagType>[] {
......
import { t } from "ttag";
import { createEntity, undo } from "metabase/lib/entities";
import { alertApi } from "metabase/api";
import {
createEntity,
entityCompatibleQuery,
undo,
} from "metabase/lib/entities";
import { addUndo } from "metabase/redux/undo";
import { AlertApi } from "metabase/services";
export const UNSUBSCRIBE = "metabase/entities/alerts/unsubscribe";
......@@ -14,6 +18,36 @@ const Alerts = createEntity({
nameOne: "alert",
path: "/api/alert",
api: {
list: (entityQuery, dispatch) =>
entityCompatibleQuery(
entityQuery,
dispatch,
alertApi.endpoints.listAlerts,
),
get: (entityQuery, options, dispatch) =>
entityCompatibleQuery(
entityQuery.id,
dispatch,
alertApi.endpoints.listAlerts,
),
create: (entityQuery, dispatch) =>
entityCompatibleQuery(
entityQuery,
dispatch,
alertApi.endpoints.createAlert,
),
update: (entityQuery, dispatch) =>
entityCompatibleQuery(
entityQuery,
dispatch,
alertApi.endpoints.updateAlert,
),
delete: () => {
throw new TypeError("Alerts.api.delete is not supported");
},
},
actionTypes: {
UNSUBSCRIBE,
},
......@@ -30,7 +64,11 @@ const Alerts = createEntity({
unsubscribe:
({ id }) =>
async dispatch => {
await AlertApi.unsubscribe({ id });
await entityCompatibleQuery(
id,
dispatch,
alertApi.endpoints.deleteAlertSubscription,
);
dispatch(addUndo({ message: t`Successfully unsubscribed` }));
dispatch({ type: UNSUBSCRIBE, payload: { id } });
dispatch({ type: Alerts.actionTypes.INVALIDATE_LISTS_ACTION });
......
......@@ -40,7 +40,7 @@ export class RestfulRequest {
trigger = params => async dispatch => {
dispatch({ type: this.actions.requestStarted });
try {
const result = await this.endpoint(params);
const result = await this.endpoint(params, dispatch);
dispatch({ type: this.actions.requestSuccessful, payload: { result } });
} catch (error) {
dispatch({ type: this.actions.requestFailed, payload: { error } });
......
......@@ -367,15 +367,6 @@ export const PulseApi = {
unsubscribe: DELETE("/api/pulse/:id/subscription"),
};
export const AlertApi = {
list: GET("/api/alert"),
list_for_question: GET("/api/alert/question/:questionId"),
get: GET("/api/alert/:id"),
create: POST("/api/alert"),
update: PUT("/api/alert/:id"),
unsubscribe: DELETE("/api/alert/:id/subscription"),
};
export const SegmentApi = {
list: GET("/api/segment"),
create: POST("/api/segment"),
......
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