Skip to content

Commit

Permalink
Add button to manually create dataset events (#38305)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbovenzi authored Mar 20, 2024
1 parent 8914ba2 commit 3f89dfe
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 2 deletions.
2 changes: 2 additions & 0 deletions airflow/www/static/js/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -102,4 +103,5 @@ export {
useEventLogs,
useCalendarData,
useTaskFails,
useCreateDatasetEvent,
};
55 changes: 55 additions & 0 deletions airflow/www/static/js/api/useCreateDatasetEvent.ts
Original file line number Diff line number Diff line change
@@ -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 }),
}
);
}
111 changes: 111 additions & 0 deletions airflow/www/static/js/datasets/CreateDatasetEvent.tsx
Original file line number Diff line number Diff line change
@@ -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;
42 changes: 40 additions & 2 deletions airflow/www/static/js/datasets/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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">
Expand All @@ -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>
);
};
Expand Down
1 change: 1 addition & 0 deletions airflow/www/templates/airflow/datasets.html
Original file line number Diff line number Diff line change
Expand Up @@ -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') }}">
Expand Down

0 comments on commit 3f89dfe

Please sign in to comment.