From 19e92f3ee0455550b75b0cc66b784ca7af88e805 Mon Sep 17 00:00:00 2001
From: Nemanja Glumac <31325167+nemanjaglumac@users.noreply.github.com>
Date: Fri, 7 Jun 2024 21:20:50 +0200
Subject: [PATCH] Use RTK Query to obtain task/job information (#43798)

* Type `TaskInfo`

* Re-write `JobInfoApp` to use RTK Query

* Re-write `JobTriggersModal` to use RTK Query

* Remove unused `fetchJobInfo` action

* Remove `TaskApi` from `services.js`

* Remove `JobApp` styled component

* Simplify fetching logic
---
 frontend/src/metabase-types/api/task.ts       | 32 +++++++++
 frontend/src/metabase/admin/routes.jsx        |  4 +-
 .../admin/tasks/containers/JobInfoApp.jsx     | 70 ++++++-------------
 .../tasks/containers/JobInfoApp.styled.tsx    | 15 ----
 .../tasks/containers/JobTriggersModal.jsx     | 58 +++++----------
 frontend/src/metabase/admin/tasks/jobInfo.js  | 11 ---
 frontend/src/metabase/api/task.ts             |  3 +-
 frontend/src/metabase/services.js             |  4 --
 8 files changed, 77 insertions(+), 120 deletions(-)
 delete mode 100644 frontend/src/metabase/admin/tasks/containers/JobInfoApp.styled.tsx
 delete mode 100644 frontend/src/metabase/admin/tasks/jobInfo.js

diff --git a/frontend/src/metabase-types/api/task.ts b/frontend/src/metabase-types/api/task.ts
index 9efa69710b7..eea2c6ae1fa 100644
--- a/frontend/src/metabase-types/api/task.ts
+++ b/frontend/src/metabase-types/api/task.ts
@@ -15,3 +15,35 @@ export interface Task {
 export type ListTasksRequest = PaginationRequest;
 
 export type ListTasksResponse = { data: Task[] } & PaginationResponse;
+
+type Trigger = {
+  description: string | null;
+  schedule: string;
+  timezone: string;
+  key: string;
+  "previous-fire-time": string | null;
+  "start-time": string;
+  "misfire-instruction": string;
+  "end-time": string | null;
+  state: string;
+  priority: number;
+  "next-fire-time": string;
+  "may-fire-again?": boolean;
+  "final-fire-time": string | null;
+  data: Record<string, unknown>;
+};
+
+type Job = {
+  key: string;
+  class: string;
+  description: string;
+  "concurrent-execution-disallowed?": boolean;
+  "durable?": boolean;
+  "requests-recovery?": boolean;
+  triggers: Trigger[];
+};
+
+export type TaskInfo = {
+  scheduler: string[];
+  jobs: Job[];
+};
diff --git a/frontend/src/metabase/admin/routes.jsx b/frontend/src/metabase/admin/routes.jsx
index 39ee91cb1f8..50f32fe8bd5 100644
--- a/frontend/src/metabase/admin/routes.jsx
+++ b/frontend/src/metabase/admin/routes.jsx
@@ -26,8 +26,8 @@ import getAdminPermissionsRoutes from "metabase/admin/permissions/routes";
 import { SettingsEditor } from "metabase/admin/settings/app/components/SettingsEditor";
 import { Help } from "metabase/admin/tasks/components/Help";
 import { Logs } from "metabase/admin/tasks/components/Logs";
-import JobInfoApp from "metabase/admin/tasks/containers/JobInfoApp";
-import JobTriggersModal from "metabase/admin/tasks/containers/JobTriggersModal";
+import { JobInfoApp } from "metabase/admin/tasks/containers/JobInfoApp";
+import { JobTriggersModal } from "metabase/admin/tasks/containers/JobTriggersModal";
 import {
   ModelCacheRefreshJobs,
   ModelCacheRefreshJobModal,
diff --git a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
index dff81f444b4..c8c9153951b 100644
--- a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
+++ b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx
@@ -1,29 +1,21 @@
 /* eslint-disable react/prop-types */
 import cx from "classnames";
-import { Component } from "react";
-import { connect } from "react-redux";
 import { t } from "ttag";
 
+import { useGetTasksInfoQuery } from "metabase/api";
 import AdminHeader from "metabase/components/AdminHeader";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
 import Link from "metabase/core/components/Link";
 import AdminS from "metabase/css/admin.module.css";
 import CS from "metabase/css/core/index.css";
-
-import { fetchJobInfo } from "../jobInfo";
-
-import {
-  JobInfoHeader,
-  JobInfoRoot,
-  JobSchedulerInfo,
-} from "./JobInfoApp.styled";
+import { Box, Flex } from "metabase/ui";
 
 const renderSchedulerInfo = scheduler => {
   return (
     scheduler && (
-      <JobSchedulerInfo>
+      <Flex align="center">
         <pre>{scheduler.join("\n")}</pre>
-      </JobSchedulerInfo>
+      </Flex>
     )
   );
 };
@@ -64,40 +56,22 @@ const renderJobsTable = jobs => {
   );
 };
 
-class JobInfoApp extends Component {
-  async componentDidMount() {
-    try {
-      const info = (await this.props.fetchJobInfo()).payload;
-      this.setState({
-        scheduler: info.scheduler,
-        jobs: info.jobs,
-        error: null,
-      });
-    } catch (error) {
-      this.setState({ error });
-    }
-  }
-
-  render() {
-    const { children } = this.props;
-    const { error, scheduler, jobs } = this.state || {};
-
-    return (
-      <LoadingAndErrorWrapper loading={!scheduler} error={error}>
-        <JobInfoRoot>
-          <JobInfoHeader>
-            <AdminHeader title={t`Scheduler Info`} />
-          </JobInfoHeader>
-          {renderSchedulerInfo(scheduler)}
-          {renderJobsTable(jobs)}
-          {
-            // render 'children' so that the invididual task modals show up
-            children
-          }
-        </JobInfoRoot>
-      </LoadingAndErrorWrapper>
-    );
-  }
-}
+export const JobInfoApp = ({ children }) => {
+  const { data, error, isFetching } = useGetTasksInfoQuery();
 
-export default connect(null, { fetchJobInfo })(JobInfoApp);
+  return (
+    <LoadingAndErrorWrapper loading={isFetching} error={error}>
+      <Box pl="md">
+        <Flex align="center">
+          <AdminHeader title={t`Scheduler Info`} />
+        </Flex>
+        {renderSchedulerInfo(data?.scheduler)}
+        {renderJobsTable(data?.jobs)}
+        {
+          // render 'children' so that the invididual task modals show up
+          children
+        }
+      </Box>
+    </LoadingAndErrorWrapper>
+  );
+};
diff --git a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.styled.tsx b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.styled.tsx
deleted file mode 100644
index 26558be7654..00000000000
--- a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.styled.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import styled from "@emotion/styled";
-
-export const JobInfoRoot = styled.div`
-  padding-left: 1rem;
-`;
-
-export const JobInfoHeader = styled.div`
-  display: flex;
-  align-items: center;
-`;
-
-export const JobSchedulerInfo = styled.div`
-  display: flex;
-  align-items: center;
-`;
diff --git a/frontend/src/metabase/admin/tasks/containers/JobTriggersModal.jsx b/frontend/src/metabase/admin/tasks/containers/JobTriggersModal.jsx
index 53dea196578..a46137799db 100644
--- a/frontend/src/metabase/admin/tasks/containers/JobTriggersModal.jsx
+++ b/frontend/src/metabase/admin/tasks/containers/JobTriggersModal.jsx
@@ -1,17 +1,15 @@
 /* eslint-disable react/prop-types */
 import cx from "classnames";
-import { Component } from "react";
-import { connect } from "react-redux";
 import { goBack } from "react-router-redux";
 import { t } from "ttag";
 import _ from "underscore";
 
+import { useGetTasksInfoQuery } from "metabase/api";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
 import ModalContent from "metabase/components/ModalContent";
 import AdminS from "metabase/css/admin.module.css";
 import CS from "metabase/css/core/index.css";
-
-import { fetchJobInfo } from "../jobInfo";
+import { useDispatch } from "metabase/lib/redux";
 
 const renderTriggersTable = triggers => {
   return (
@@ -53,40 +51,22 @@ const renderTriggersTable = triggers => {
   );
 };
 
-class JobTriggersModal extends Component {
-  state = {
-    triggers: null,
-    error: null,
-  };
-
-  async componentDidMount() {
-    try {
-      const { jobKey } = this.props.params;
-      const jobs = jobKey && (await this.props.fetchJobInfo()).payload.jobs;
-      const job = jobs && _.findWhere(jobs, { key: jobKey });
-      const triggers = (job && job.triggers) || [];
-
-      this.setState({ triggers, error: null });
-    } catch (error) {
-      this.setState({ error });
-    }
-  }
-
-  render() {
-    const {
-      params: { jobKey },
-      goBack,
-    } = this.props;
-    const { triggers, error } = this.state;
+export const JobTriggersModal = props => {
+  const dispatch = useDispatch();
+  const { data, error, isFetching } = useGetTasksInfoQuery();
 
-    return (
-      <ModalContent title={t`Triggers for ${jobKey}`} onClose={goBack}>
-        <LoadingAndErrorWrapper loading={!triggers} error={error}>
-          {() => renderTriggersTable(triggers)}
-        </LoadingAndErrorWrapper>
-      </ModalContent>
-    );
-  }
-}
+  const { jobKey } = props.params;
+  const jobs = jobKey && data?.jobs;
+  const job = jobs && _.findWhere(jobs, { key: jobKey });
 
-export default connect(null, { fetchJobInfo, goBack })(JobTriggersModal);
+  return (
+    <ModalContent
+      title={t`Triggers for ${jobKey}`}
+      onClose={() => dispatch(goBack())}
+    >
+      <LoadingAndErrorWrapper loading={isFetching} error={error}>
+        {() => renderTriggersTable(job?.triggers)}
+      </LoadingAndErrorWrapper>
+    </ModalContent>
+  );
+};
diff --git a/frontend/src/metabase/admin/tasks/jobInfo.js b/frontend/src/metabase/admin/tasks/jobInfo.js
deleted file mode 100644
index 3300110c3e8..00000000000
--- a/frontend/src/metabase/admin/tasks/jobInfo.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { createThunkAction } from "metabase/lib/redux";
-import { TaskApi } from "metabase/services";
-
-export const FETCH_JOB_INFO = "metabase/admin/tasks/FETCH_JOB_INFO";
-
-export const fetchJobInfo = createThunkAction(
-  FETCH_JOB_INFO,
-  () => async () => {
-    return await TaskApi.getJobsInfo();
-  },
-);
diff --git a/frontend/src/metabase/api/task.ts b/frontend/src/metabase/api/task.ts
index 41e9bbb6184..32757b71e96 100644
--- a/frontend/src/metabase/api/task.ts
+++ b/frontend/src/metabase/api/task.ts
@@ -2,6 +2,7 @@ import type {
   ListTasksRequest,
   ListTasksResponse,
   Task,
+  TaskInfo,
 } from "metabase-types/api";
 
 import { Api } from "./api";
@@ -25,7 +26,7 @@ export const taskApi = Api.injectEndpoints({
       }),
       providesTags: task => (task ? provideTaskTags(task) : []),
     }),
-    getTasksInfo: builder.query<unknown, void>({
+    getTasksInfo: builder.query<TaskInfo, void>({
       query: () => ({
         method: "GET",
         url: "/api/task/info",
diff --git a/frontend/src/metabase/services.js b/frontend/src/metabase/services.js
index 4f856a3f0ea..5231aecfc3e 100644
--- a/frontend/src/metabase/services.js
+++ b/frontend/src/metabase/services.js
@@ -457,10 +457,6 @@ export const I18NApi = {
   locale: GET("/app/locales/:locale.json"),
 };
 
-export const TaskApi = {
-  getJobsInfo: GET("/api/task/info"),
-};
-
 export function setPublicQuestionEndpoints(uuid) {
   setCardEndpoints("/api/public/card/:uuid", { uuid });
 }
-- 
GitLab