This is an automated email from the ASF dual-hosted git repository.

phanikumv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 3f89dfe3e6 Add button to manually create dataset events (#38305)
3f89dfe3e6 is described below

commit 3f89dfe3e6fee466ff1e4ca300ad6dd027bdcfa8
Author: Brent Bovenzi <[email protected]>
AuthorDate: Wed Mar 20 05:46:13 2024 -0700

    Add button to manually create dataset events (#38305)
---
 airflow/www/static/js/api/index.ts                 |   2 +
 airflow/www/static/js/api/useCreateDatasetEvent.ts |  55 ++++++++++
 .../www/static/js/datasets/CreateDatasetEvent.tsx  | 111 +++++++++++++++++++++
 airflow/www/static/js/datasets/Details.tsx         |  42 +++++++-
 airflow/www/templates/airflow/datasets.html        |   1 +
 5 files changed, 209 insertions(+), 2 deletions(-)

diff --git a/airflow/www/static/js/api/index.ts 
b/airflow/www/static/js/api/index.ts
index 4f883b4825..1c27af8a3d 100644
--- a/airflow/www/static/js/api/index.ts
+++ b/airflow/www/static/js/api/index.ts
@@ -53,6 +53,7 @@ import { useTaskXcomEntry, useTaskXcomCollection } from 
"./useTaskXcom";
 import useEventLogs from "./useEventLogs";
 import useCalendarData from "./useCalendarData";
 import useTaskFails from "./useTaskFails";
+import useCreateDatasetEvent from "./useCreateDatasetEvent";
 
 axios.interceptors.request.use((config) => {
   config.paramsSerializer = {
@@ -102,4 +103,5 @@ export {
   useEventLogs,
   useCalendarData,
   useTaskFails,
+  useCreateDatasetEvent,
 };
diff --git a/airflow/www/static/js/api/useCreateDatasetEvent.ts 
b/airflow/www/static/js/api/useCreateDatasetEvent.ts
new file mode 100644
index 0000000000..f14b35ee37
--- /dev/null
+++ b/airflow/www/static/js/api/useCreateDatasetEvent.ts
@@ -0,0 +1,55 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import axios, { AxiosResponse } from "axios";
+import { useMutation, useQueryClient } from "react-query";
+
+import { getMetaValue } from "src/utils";
+import type { API } from "src/types";
+import useErrorToast from "src/utils/useErrorToast";
+
+interface Props {
+  datasetId?: number;
+  uri?: string;
+}
+
+const createDatasetUrl = getMetaValue("create_dataset_event_api");
+
+export default function useCreateDatasetEvent({ datasetId, uri }: Props) {
+  const queryClient = useQueryClient();
+  const errorToast = useErrorToast();
+
+  return useMutation(
+    ["createDatasetEvent", uri],
+    (extra?: API.DatasetEvent["extra"]) =>
+      axios.post<AxiosResponse, API.CreateDatasetEventVariables>(
+        createDatasetUrl,
+        {
+          dataset_uri: uri,
+          extra: extra || {},
+        }
+      ),
+    {
+      onSuccess: () => {
+        queryClient.invalidateQueries(["datasets-events", datasetId]);
+      },
+      onError: (error: Error) => errorToast({ error }),
+    }
+  );
+}
diff --git a/airflow/www/static/js/datasets/CreateDatasetEvent.tsx 
b/airflow/www/static/js/datasets/CreateDatasetEvent.tsx
new file mode 100644
index 0000000000..6a1249e32c
--- /dev/null
+++ b/airflow/www/static/js/datasets/CreateDatasetEvent.tsx
@@ -0,0 +1,111 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState } from "react";
+import {
+  Button,
+  FormControl,
+  FormErrorMessage,
+  FormLabel,
+  Modal,
+  ModalBody,
+  ModalCloseButton,
+  ModalContent,
+  ModalFooter,
+  ModalHeader,
+  ModalOverlay,
+  Textarea,
+} from "@chakra-ui/react";
+
+import { useContainerRef } from "src/context/containerRef";
+import { useCreateDatasetEvent } from "src/api";
+import type { Dataset } from "src/types/api-generated";
+
+interface Props {
+  isOpen: boolean;
+  onClose: () => void;
+  dataset: Dataset;
+}
+
+function checkJsonString(str: string) {
+  try {
+    JSON.parse(str);
+  } catch (e) {
+    return false;
+  }
+  return true;
+}
+
+const CreateDatasetEventModal = ({ dataset, isOpen, onClose }: Props) => {
+  const containerRef = useContainerRef();
+  const [extra, setExtra] = useState("");
+
+  const isJson = checkJsonString(extra);
+  const isDisabled = !!extra && !isJson;
+
+  const { mutate: createDatasetEvent, isLoading } = useCreateDatasetEvent({
+    datasetId: dataset.id,
+    uri: dataset.uri,
+  });
+
+  const onSubmit = () => {
+    createDatasetEvent(extra ? JSON.parse(extra) : undefined);
+    onClose();
+  };
+
+  return (
+    <Modal
+      size="xl"
+      isOpen={isOpen}
+      onClose={onClose}
+      portalProps={{ containerRef }}
+    >
+      <ModalOverlay />
+      <ModalContent>
+        <ModalHeader>Manually create event for {dataset.uri}</ModalHeader>
+        <ModalCloseButton />
+        <ModalBody>
+          <FormControl isInvalid={isDisabled}>
+            <FormLabel>Extra (optional)</FormLabel>
+            <Textarea
+              value={extra}
+              onChange={(e) => setExtra(e.target.value)}
+            />
+            <FormErrorMessage>Extra needs to be valid JSON</FormErrorMessage>
+          </FormControl>
+        </ModalBody>
+        <ModalFooter justifyContent="space-between">
+          <Button colorScheme="gray" onClick={onClose}>
+            Cancel
+          </Button>
+          <Button
+            colorScheme="blue"
+            disabled={isDisabled}
+            onClick={onSubmit}
+            isLoading={isLoading}
+          >
+            Create
+          </Button>
+        </ModalFooter>
+      </ModalContent>
+    </Modal>
+  );
+};
+
+export default CreateDatasetEventModal;
diff --git a/airflow/www/static/js/datasets/Details.tsx 
b/airflow/www/static/js/datasets/Details.tsx
index 682f54d638..cb0f83fd45 100644
--- a/airflow/www/static/js/datasets/Details.tsx
+++ b/airflow/www/static/js/datasets/Details.tsx
@@ -18,11 +18,24 @@
  */
 
 import React from "react";
-import { Box, Heading, Flex, Spinner, Button } from "@chakra-ui/react";
+import {
+  Box,
+  Heading,
+  Flex,
+  Spinner,
+  Button,
+  IconButton,
+  useDisclosure,
+} from "@chakra-ui/react";
+import { MdPlayArrow } from "react-icons/md";
 
 import { useDataset } from "src/api";
 import { ClipboardButton } from "src/components/Clipboard";
 import InfoTooltip from "src/components/InfoTooltip";
+import { useContainerRef } from "src/context/containerRef";
+import Tooltip from "src/components/Tooltip";
+
+import CreateDatasetEventModal from "./CreateDatasetEvent";
 import Events from "./DatasetEvents";
 
 interface Props {
@@ -32,9 +45,27 @@ interface Props {
 
 const DatasetDetails = ({ uri, onBack }: Props) => {
   const { data: dataset, isLoading } = useDataset({ uri });
+  const { isOpen, onToggle, onClose } = useDisclosure();
+  const containerRef = useContainerRef();
   return (
     <Box mt={[6, 3]}>
-      <Button onClick={onBack}>See all datasets</Button>
+      <Flex alignItems="center" justifyContent="space-between">
+        <Button onClick={onBack}>See all datasets</Button>
+        <Tooltip
+          label="Manually create dataset event"
+          hasArrow
+          portalProps={{ containerRef }}
+        >
+          <IconButton
+            variant="outline"
+            colorScheme="blue"
+            aria-label="Manually create dataset event"
+            onClick={onToggle}
+          >
+            <MdPlayArrow />
+          </IconButton>
+        </Tooltip>
+      </Flex>
       {isLoading && <Spinner display="block" />}
       <Box>
         <Heading my={2} fontWeight="normal" size="lg">
@@ -52,6 +83,13 @@ const DatasetDetails = ({ uri, onBack }: Props) => {
         />
       </Flex>
       {dataset && dataset.id && <Events datasetId={dataset.id} />}
+      {dataset && (
+        <CreateDatasetEventModal
+          isOpen={isOpen}
+          onClose={onClose}
+          dataset={dataset}
+        />
+      )}
     </Box>
   );
 };
diff --git a/airflow/www/templates/airflow/datasets.html 
b/airflow/www/templates/airflow/datasets.html
index 164aaf0006..ea9295c057 100644
--- a/airflow/www/templates/airflow/datasets.html
+++ b/airflow/www/templates/airflow/datasets.html
@@ -26,6 +26,7 @@
   <meta name="datasets_summary" content="{{ 
url_for('Airflow.datasets_summary') }}">
   <meta name="dataset_api" content="{{ 
url_for('/api/v1.airflow_api_connexion_endpoints_dataset_endpoint_get_dataset', 
uri='__URI__') }}">
   <meta name="dataset_events_api" content="{{ 
url_for('/api/v1.airflow_api_connexion_endpoints_dataset_endpoint_get_dataset_events')
 }}">
+  <meta name="create_dataset_event_api" content="{{ 
url_for('/api/v1.airflow_api_connexion_endpoints_dataset_endpoint_create_dataset_event')
 }}" >
   <meta name="grid_url" content="{{ url_for('Airflow.grid', 
dag_id='__DAG_ID__') }}">
   <meta name="datasets_docs" content="{{ 
get_docs_url('concepts/datasets.html') }}">
   <meta name="dataset_dependencies_url" content="{{ 
url_for('Airflow.dataset_dependencies') }}">

Reply via email to