Skip to content

Commit

Permalink
Add is_paused toggle (#42621)
Browse files Browse the repository at this point in the history
* Add pause/unpause DAG toggle

* wire up onSuccess handler

* Refactor query names
  • Loading branch information
bbovenzi authored Oct 2, 2024
1 parent 0c911a9 commit 6fa3319
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 44 deletions.
1 change: 1 addition & 0 deletions airflow/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"devDependencies": {
"@7nohe/openapi-react-query-codegen": "^1.6.0",
"@eslint/compat": "^1.1.1",
"@eslint/js": "^9.10.0",
"@stylistic/eslint-plugin": "^2.8.0",
"@tanstack/eslint-plugin-query": "^5.52.0",
Expand Down
9 changes: 9 additions & 0 deletions airflow/ui/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion airflow/ui/rules/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
/**
* @import { FlatConfig } from "@typescript-eslint/utils/ts-eslint";
*/
import { fixupPluginRules } from "@eslint/compat";
import jsxA11y from "eslint-plugin-jsx-a11y";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
Expand Down Expand Up @@ -57,7 +58,7 @@ export const reactRefreshNamespace = "react-refresh";
export const reactRules = /** @type {const} @satisfies {FlatConfig.Config} */ ({
plugins: {
[jsxA11yNamespace]: jsxA11y,
[reactHooksNamespace]: reactHooks,
[reactHooksNamespace]: fixupPluginRules(reactHooks),
[reactNamespace]: react,
[reactRefreshNamespace]: reactRefresh,
},
Expand Down
2 changes: 1 addition & 1 deletion airflow/ui/src/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const DataTable = <TData,>({
return (
<TableContainer maxH="calc(100vh - 10rem)" overflowY="auto">
<ChakraTable colorScheme="blue">
<Thead bg={theadBg} position="sticky" top={0}>
<Thead bg={theadBg} position="sticky" top={0} zIndex={1}>
{table.getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map(
Expand Down
56 changes: 56 additions & 0 deletions airflow/ui/src/components/TogglePause.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*!
* 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 { Switch } from "@chakra-ui/react";
import { useQueryClient } from "@tanstack/react-query";
import { useCallback } from "react";

import {
useDagServiceGetDagsKey,
useDagServicePatchDag,
} from "openapi/queries";

type Props = {
readonly dagId: string;
readonly isPaused: boolean;
};

export const TogglePause = ({ dagId, isPaused }: Props) => {
const queryClient = useQueryClient();

const onSuccess = async () => {
await queryClient.invalidateQueries({
queryKey: [useDagServiceGetDagsKey],
});
};

const { mutate } = useDagServicePatchDag({
onSuccess,
});

const onChange = useCallback(() => {
mutate({
dagId,
requestBody: {
is_paused: !isPaused,
},
});
}, [dagId, isPaused, mutate]);

return <Switch isChecked={!isPaused} onChange={onChange} size="sm" />;
};
86 changes: 86 additions & 0 deletions airflow/ui/src/pages/DagsList/DagsFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*!
* 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 { HStack, Select, Text, Box } from "@chakra-ui/react";
import { Select as ReactSelect } from "chakra-react-select";
import { useCallback } from "react";
import { useSearchParams } from "react-router-dom";

import { useTableURLState } from "src/components/DataTable/useTableUrlState";
import { QuickFilterButton } from "src/components/QuickFilterButton";

const PAUSED_PARAM = "paused";

export const DagsFilters = () => {
const [searchParams, setSearchParams] = useSearchParams();

const showPaused = searchParams.get(PAUSED_PARAM);

const { setTableURLState, tableURLState } = useTableURLState();
const { pagination, sorting } = tableURLState;

const handlePausedChange: React.ChangeEventHandler<HTMLSelectElement> =
useCallback(
({ target: { value } }) => {
if (value === "All") {
searchParams.delete(PAUSED_PARAM);
} else {
searchParams.set(PAUSED_PARAM, value);
}
setSearchParams(searchParams);
setTableURLState({
pagination: { ...pagination, pageIndex: 0 },
sorting,
});
},
[pagination, searchParams, setSearchParams, setTableURLState, sorting],
);

return (
<HStack justifyContent="space-between">
<HStack spacing={4}>
<Box>
<Text fontSize="sm" fontWeight={200} mb={1}>
State:
</Text>
<HStack>
<QuickFilterButton isActive>All</QuickFilterButton>
<QuickFilterButton isDisabled>Failed</QuickFilterButton>
<QuickFilterButton isDisabled>Running</QuickFilterButton>
<QuickFilterButton isDisabled>Successful</QuickFilterButton>
</HStack>
</Box>
<Box>
<Text fontSize="sm" fontWeight={200} mb={1}>
Active:
</Text>
<Select
onChange={handlePausedChange}
value={showPaused ?? undefined}
variant="flushed"
>
<option>All</option>
<option value="false">Enabled DAGs</option>
<option value="true">Disabled DAGs</option>
</Select>
</Box>
</HStack>
<ReactSelect isDisabled placeholder="Filter by tag" />
</HStack>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,43 @@
*/
import {
Badge,
Checkbox,
Heading,
HStack,
Select,
Spinner,
VStack,
} from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
import { Select as ReactSelect } from "chakra-react-select";
import { type ChangeEventHandler, useCallback } from "react";
import { useSearchParams } from "react-router-dom";

import { useDagServiceGetDags } from "openapi/queries";
import type { DAGResponse } from "openapi/requests/types.gen";
import { DataTable } from "src/components/DataTable";
import { useTableURLState } from "src/components/DataTable/useTableUrlState";
import { SearchBar } from "src/components/SearchBar";
import { TogglePause } from "src/components/TogglePause";
import { pluralize } from "src/utils/pluralize";

import { DataTable } from "../components/DataTable";
import { useTableURLState } from "../components/DataTable/useTableUrlState";
import { QuickFilterButton } from "../components/QuickFilterButton";
import { SearchBar } from "../components/SearchBar";
import { pluralize } from "../utils/pluralize";
import { DagsFilters } from "./DagsFilters";

const columns: Array<ColumnDef<DAGResponse>> = [
{
accessorKey: "is_paused",
cell: ({ row }) => (
<TogglePause
dagId={row.original.dag_id}
isPaused={row.original.is_paused}
/>
),
enableSorting: false,
header: "",
},
{
accessorKey: "dag_id",
cell: ({ row }) => row.original.dag_display_name,
header: "DAG",
},
{
accessorKey: "is_paused",
enableSorting: false,
header: () => "Is Paused",
},
{
accessorKey: "timetable_description",
cell: (info) =>
Expand Down Expand Up @@ -82,9 +87,9 @@ const PAUSED_PARAM = "paused";

// eslint-disable-next-line complexity
export const DagsList = ({ cardView = false }) => {
const [searchParams, setSearchParams] = useSearchParams();
const [searchParams] = useSearchParams();

const showPaused = searchParams.get(PAUSED_PARAM) === "true";
const showPaused = searchParams.get(PAUSED_PARAM);

const { setTableURLState, tableURLState } = useTableURLState();
const { pagination, sorting } = tableURLState;
Expand All @@ -98,22 +103,9 @@ export const DagsList = ({ cardView = false }) => {
offset: pagination.pageIndex * pagination.pageSize,
onlyActive: true,
orderBy,
paused: showPaused,
paused: showPaused === null ? undefined : showPaused === "true",
});

const handlePausedChange = useCallback(() => {
searchParams[showPaused ? "delete" : "set"](PAUSED_PARAM, "true");
setSearchParams(searchParams);
setTableURLState({ pagination: { ...pagination, pageIndex: 0 }, sorting });
}, [
pagination,
searchParams,
setSearchParams,
setTableURLState,
showPaused,
sorting,
]);

const handleSortChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
({ currentTarget: { value } }) => {
setTableURLState({
Expand All @@ -136,20 +128,7 @@ export const DagsList = ({ cardView = false }) => {
buttonProps={{ isDisabled: true }}
inputProps={{ isDisabled: true }}
/>
<HStack justifyContent="space-between">
<HStack>
<HStack>
<QuickFilterButton isActive>All</QuickFilterButton>
<QuickFilterButton isDisabled>Failed</QuickFilterButton>
<QuickFilterButton isDisabled>Running</QuickFilterButton>
<QuickFilterButton isDisabled>Successful</QuickFilterButton>
</HStack>
<Checkbox isChecked={showPaused} onChange={handlePausedChange}>
Show Paused DAGs
</Checkbox>
</HStack>
<ReactSelect isDisabled placeholder="Filter by tag" />
</HStack>
<DagsFilters />
<HStack justifyContent="space-between">
<Heading size="md">
{pluralize("DAG", data?.total_entries)}
Expand Down
20 changes: 20 additions & 0 deletions airflow/ui/src/pages/DagsList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*!
* 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.
*/

export { DagsList } from "./DagsList";

0 comments on commit 6fa3319

Please sign in to comment.