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') }}">