You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by bb...@apache.org on 2022/03/08 20:57:17 UTC
[airflow] 08/11: use API
This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch mapped-task-drawer
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit e9ab6119a09fcf5a7b0907376cc157dfc8c80cdd
Author: Brent Bovenzi <br...@gmail.com>
AuthorDate: Tue Mar 1 19:44:16 2022 -0500
use API
---
airflow/www/package.json | 1 +
airflow/www/static/js/tree/StatusBox.jsx | 6 +-
airflow/www/static/js/tree/Tree.jsx | 16 ++--
.../tree/{details/content/Dag.jsx => api/index.js} | 21 +++--
.../{details/content/Dag.jsx => api/useDag.js} | 19 ++--
.../{details/content/Dag.jsx => api/useTasks.js} | 19 ++--
airflow/www/static/js/tree/dagRuns/Bar.jsx | 2 +-
airflow/www/static/js/tree/details/Header.jsx | 5 +-
airflow/www/static/js/tree/details/content/Dag.jsx | 36 ++++++--
.../js/tree/details/content/TaskInstance.jsx | 100 +++++++++++----------
airflow/www/static/js/tree/details/index.jsx | 34 ++++---
airflow/www/static/js/tree/index.jsx | 8 ++
airflow/www/yarn.lock | 12 +++
13 files changed, 168 insertions(+), 111 deletions(-)
diff --git a/airflow/www/package.json b/airflow/www/package.json
index c5a4f3a..717d5b1 100644
--- a/airflow/www/package.json
+++ b/airflow/www/package.json
@@ -74,6 +74,7 @@
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11",
+ "axios": "^0.26.0",
"bootstrap-3-typeahead": "^4.0.2",
"camelcase-keys": "^7.0.0",
"codemirror": "^5.59.1",
diff --git a/airflow/www/static/js/tree/StatusBox.jsx b/airflow/www/static/js/tree/StatusBox.jsx
index 9609d9a..62762c5 100644
--- a/airflow/www/static/js/tree/StatusBox.jsx
+++ b/airflow/www/static/js/tree/StatusBox.jsx
@@ -32,7 +32,7 @@ import InstanceTooltip from './InstanceTooltip';
const StatusBox = ({
group, instance, containerRef, onSelect, selected, ...rest
}) => {
- const { runId } = instance;
+ const { runId, taskId } = instance;
const { colors } = useTheme();
const hoverBlue = `${colors.blue[100]}50`;
@@ -51,7 +51,9 @@ const StatusBox = ({
const onClick = (e) => {
e.stopPropagation();
onMouseLeave();
- onSelect(instance);
+ onSelect({
+ taskId, runId, instance, task: group,
+ });
};
return (
diff --git a/airflow/www/static/js/tree/Tree.jsx b/airflow/www/static/js/tree/Tree.jsx
index 1f60d03..aeeaeb5 100644
--- a/airflow/www/static/js/tree/Tree.jsx
+++ b/airflow/www/static/js/tree/Tree.jsx
@@ -63,14 +63,14 @@ const Tree = () => {
<Flex pl="24px" position="relative" flexDirection="row" justifyContent="space-between" ref={containerRef}>
<Text transform="rotate(-90deg)" position="absolute" left="-6px" top="130px">Runs</Text>
<Text transform="rotate(-90deg)" position="absolute" left="-6px" top="190px">Tasks</Text>
- <Box mr="12px" pb="12px" overflowX="auto" ref={scrollRef} maxWidth="300px" minWidth="300px" position="relative">
- <FormControl display="flex" alignItems="center" justifyContent="flex-end" width="100%" mb={2}>
- {isRefreshOn && <Spinner color="blue.500" speed="1s" mr="4px" />}
- <FormLabel htmlFor="auto-refresh" mb={0} fontSize="12px" fontWeight="normal">
- Auto-refresh
- </FormLabel>
- <Switch id="auto-refresh" onChange={onToggleRefresh} isChecked={isRefreshOn} size="lg" />
- </FormControl>
+ <FormControl display="flex" position="absolute" left="220px">
+ {isRefreshOn && <Spinner color="blue.500" speed="1s" mr="4px" />}
+ <FormLabel htmlFor="auto-refresh" mb={0} fontSize="12px" fontWeight="normal">
+ Auto-refresh
+ </FormLabel>
+ <Switch id="auto-refresh" onChange={onToggleRefresh} isChecked={isRefreshOn} size="lg" />
+ </FormControl>
+ <Box mr="12px" pb="12px" overflowX="auto" ref={scrollRef} maxWidth="300px" minWidth="300px" position="relative" mt="24px">
<Table height={0}>
<Thead>
<DagRuns
diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/api/index.js
similarity index 72%
copy from airflow/www/static/js/tree/details/content/Dag.jsx
copy to airflow/www/static/js/tree/api/index.js
index be460ba..58edd88 100644
--- a/airflow/www/static/js/tree/details/content/Dag.jsx
+++ b/airflow/www/static/js/tree/api/index.js
@@ -17,15 +17,18 @@
* under the License.
*/
-import React from 'react';
-import {
- Text,
-} from '@chakra-ui/react';
+import axios from 'axios';
+import camelcaseKeys from 'camelcase-keys';
-const Dag = () => (
- <>
- <Text>dag details</Text>
- </>
+import useDag from './useDag';
+import useTasks from './useTasks';
+
+axios.defaults.baseURL = '/api/v1';
+axios.interceptors.response.use(
+ (res) => (res.data ? camelcaseKeys(res.data, { deep: true }) : res),
);
-export default Dag;
+export {
+ useDag,
+ useTasks,
+};
diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/api/useDag.js
similarity index 80%
copy from airflow/www/static/js/tree/details/content/Dag.jsx
copy to airflow/www/static/js/tree/api/useDag.js
index be460ba..6c19ee4 100644
--- a/airflow/www/static/js/tree/details/content/Dag.jsx
+++ b/airflow/www/static/js/tree/api/useDag.js
@@ -17,15 +17,12 @@
* under the License.
*/
-import React from 'react';
-import {
- Text,
-} from '@chakra-ui/react';
+import axios from 'axios';
+import { useQuery } from 'react-query';
-const Dag = () => (
- <>
- <Text>dag details</Text>
- </>
-);
-
-export default Dag;
+export default function useDag(dagId) {
+ return useQuery(
+ ['dag', dagId],
+ () => axios.get(`/dags/${dagId}`),
+ );
+}
diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/api/useTasks.js
similarity index 80%
copy from airflow/www/static/js/tree/details/content/Dag.jsx
copy to airflow/www/static/js/tree/api/useTasks.js
index be460ba..3dd11e30 100644
--- a/airflow/www/static/js/tree/details/content/Dag.jsx
+++ b/airflow/www/static/js/tree/api/useTasks.js
@@ -17,15 +17,12 @@
* under the License.
*/
-import React from 'react';
-import {
- Text,
-} from '@chakra-ui/react';
+import axios from 'axios';
+import { useQuery } from 'react-query';
-const Dag = () => (
- <>
- <Text>dag details</Text>
- </>
-);
-
-export default Dag;
+export default function useTasks(dagId) {
+ return useQuery(
+ ['tasks', dagId],
+ () => axios.get(`/dags/${dagId}/tasks`),
+ );
+}
diff --git a/airflow/www/static/js/tree/dagRuns/Bar.jsx b/airflow/www/static/js/tree/dagRuns/Bar.jsx
index 1c8dd6b..94c84e7 100644
--- a/airflow/www/static/js/tree/dagRuns/Bar.jsx
+++ b/airflow/www/static/js/tree/dagRuns/Bar.jsx
@@ -59,7 +59,7 @@ const DagRunBar = ({
zIndex={1}
onClick={(e) => {
e.stopPropagation();
- onSelect(run);
+ onSelect({ runId: run.runId, dagRun: run });
}}
position="relative"
data-peer
diff --git a/airflow/www/static/js/tree/details/Header.jsx b/airflow/www/static/js/tree/details/Header.jsx
index b9d3086..c362606 100644
--- a/airflow/www/static/js/tree/details/Header.jsx
+++ b/airflow/www/static/js/tree/details/Header.jsx
@@ -27,9 +27,8 @@ import {
} from '@chakra-ui/react';
import { MdPlayArrow } from 'react-icons/md';
-import useTreeData from '../useTreeData';
import { formatDateTime } from '../../datetime_utils';
-import getMetaValue from '../../meta_value';
+import { getMetaValue } from '../../utils';
const dagId = getMetaValue('dag_id');
@@ -43,8 +42,8 @@ const LabelValue = ({ label, value }) => (
const Header = ({
selected: { taskId, runId },
onSelect,
+ dagRuns,
}) => {
- const { data: { dagRuns = [] } } = useTreeData();
const dagRun = dagRuns.find((r) => r.runId === runId);
// console.log(dagRun);
let runLabel = dagRun ? formatDateTime(dagRun.dataIntervalEnd) : '';
diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/details/content/Dag.jsx
index be460ba..e71d9c2 100644
--- a/airflow/www/static/js/tree/details/content/Dag.jsx
+++ b/airflow/www/static/js/tree/details/content/Dag.jsx
@@ -20,12 +20,38 @@
import React from 'react';
import {
Text,
+ Tag,
+ Code,
+ Flex,
+ HStack,
} from '@chakra-ui/react';
-const Dag = () => (
- <>
- <Text>dag details</Text>
- </>
-);
+import { getMetaValue } from '../../../utils';
+import { useDag } from '../../api';
+
+const dagId = getMetaValue('dag_id');
+
+const Dag = () => {
+ const { data: dag } = useDag(dagId);
+ if (!dag) return null;
+ const {
+ description, tags, fileloc, owners,
+ } = dag;
+ return (
+ <>
+ {description && <Text>{description}</Text>}
+ <HStack>{tags.map((tag) => <Tag key={tag.name} size="lg">{tag.name}</Tag>)}</HStack>
+ <Text>
+ Relative File Location:
+ {' '}
+ <Code colorScheme="blackAlpha">{fileloc}</Code>
+ </Text>
+ <Flex>
+ <Text mr={2}>Owner:</Text>
+ {owners.map((o) => <Text key={o}>{o}</Text>)}
+ </Flex>
+ </>
+ );
+};
export default Dag;
diff --git a/airflow/www/static/js/tree/details/content/TaskInstance.jsx b/airflow/www/static/js/tree/details/content/TaskInstance.jsx
index bc60737..0c0a21e 100644
--- a/airflow/www/static/js/tree/details/content/TaskInstance.jsx
+++ b/airflow/www/static/js/tree/details/content/TaskInstance.jsx
@@ -24,68 +24,70 @@ import {
Text,
Box,
} from '@chakra-ui/react';
+import { finalStatesMap } from '../../../utils';
import { formatDateTime, getDuration, formatDuration } from '../../../datetime_utils';
const TaskInstance = ({
instance: {
- duration, operator, startDate, endDate, state, taskId, runId, // mappedStates,
+ duration, operator, startDate, endDate, state, taskId, runId, mappedStates,
},
+ task,
}) => {
- const isGroup = false; // !!group.children;
+ const isGroup = !!task.children;
const groupSummary = [];
- // const mapSummary = [];
+ const mapSummary = [];
- // if (isGroup) {
- // const numMap = finalStatesMap();
- // group.children.forEach((child) => {
- // const taskInstance = child.instances.find((ti) => ti.runId === runId);
- // if (taskInstance) {
- // const stateKey = taskInstance.state == null ? 'no_status' : taskInstance.state;
- // if (numMap.has(stateKey)) numMap.set(stateKey, numMap.get(stateKey) + 1);
- // }
- // });
- // numMap.forEach((key, val) => {
- // if (key > 0) {
- // groupSummary.push(
- // // eslint-disable-next-line react/no-array-index-key
- // <Text key={val} ml="10px">
- // {val}
- // {': '}
- // {key}
- // </Text>,
- // );
- // }
- // });
- // }
+ if (isGroup) {
+ const numMap = finalStatesMap();
+ task.children.forEach((child) => {
+ const taskInstance = child.instances.find((ti) => ti.runId === runId);
+ if (taskInstance) {
+ const stateKey = taskInstance.state == null ? 'no_status' : taskInstance.state;
+ if (numMap.has(stateKey)) numMap.set(stateKey, numMap.get(stateKey) + 1);
+ }
+ });
+ numMap.forEach((key, val) => {
+ if (key > 0) {
+ groupSummary.push(
+ // eslint-disable-next-line react/no-array-index-key
+ <Text key={val} ml="10px">
+ {val}
+ {': '}
+ {key}
+ </Text>,
+ );
+ }
+ });
+ }
- // if (group.isMapped && mappedStates) {
- // const numMap = finalStatesMap();
- // mappedStates.forEach((s) => {
- // const stateKey = s || 'no_status';
- // if (numMap.has(stateKey)) numMap.set(stateKey, numMap.get(stateKey) + 1);
- // });
- // numMap.forEach((key, val) => {
- // if (key > 0) {
- // mapSummary.push(
- // // eslint-disable-next-line react/no-array-index-key
- // <Text key={val} ml="10px">
- // {val}
- // {': '}
- // {key}
- // </Text>,
- // );
- // }
- // });
- // }
+ if (task.isMapped && mappedStates) {
+ const numMap = finalStatesMap();
+ mappedStates.forEach((s) => {
+ const stateKey = s || 'no_status';
+ if (numMap.has(stateKey)) numMap.set(stateKey, numMap.get(stateKey) + 1);
+ });
+ numMap.forEach((key, val) => {
+ if (key > 0) {
+ mapSummary.push(
+ // eslint-disable-next-line react/no-array-index-key
+ <Text key={val} ml="10px">
+ {val}
+ {': '}
+ {key}
+ </Text>,
+ );
+ }
+ });
+ }
const taskIdTitle = isGroup ? 'Task Group Id: ' : 'Task Id: ';
return (
<Box fontSize="12px" py="4px">
- {/* {group.tooltip && (
- <Text>{group.tooltip}</Text>
- )} */}
+ {task.tooltip && (
+ <Text>{task.tooltip}</Text>
+ )}
<Text>
<Text as="strong">Status:</Text>
{' '}
@@ -98,7 +100,7 @@ const TaskInstance = ({
{groupSummary}
</>
)}
- {/* {group.isMapped && (
+ {task.isMapped && (
<>
<br />
<Text as="strong">
@@ -109,7 +111,7 @@ const TaskInstance = ({
</Text>
{mapSummary}
</>
- )} */}
+ )}
<br />
<Text>
{taskIdTitle}
diff --git a/airflow/www/static/js/tree/details/index.jsx b/airflow/www/static/js/tree/details/index.jsx
index 31610a7..ee3b044 100644
--- a/airflow/www/static/js/tree/details/index.jsx
+++ b/airflow/www/static/js/tree/details/index.jsx
@@ -28,21 +28,31 @@ import Header from './Header';
import TaskInstanceContent from './content/TaskInstance';
import DagRunContent from './content/DagRun';
import DagContent from './content/Dag';
+import useTreeData from '../useTreeData';
const Details = ({
selected,
onSelect,
-}) => (
- <Flex borderLeftWidth="1px" flexDirection="column" p={3} flexGrow={1}>
- <Header selected={selected} onSelect={onSelect} />
- <Divider my={2} />
- <Box>
- {/* TODO: get full instance data from the API */}
- {!selected.runId && !selected.taskId && <DagContent />}
- {selected.runId && !selected.taskId && <DagRunContent dagRun={selected} />}
- {selected.taskId && <TaskInstanceContent instance={selected} />}
- </Box>
- </Flex>
-);
+}) => {
+ const { data: { dagRuns = [] } } = useTreeData();
+ console.log(selected);
+ return (
+ <Flex borderLeftWidth="1px" flexDirection="column" p={3} flexGrow={1}>
+ <Header selected={selected} onSelect={onSelect} dagRuns={dagRuns} />
+ <Divider my={2} />
+ <Box>
+ {/* TODO: get full instance data from the API */}
+ {!selected.runId && !selected.taskId && <DagContent />}
+ {selected.runId && !selected.taskId && <DagRunContent dagRun={selected.dagRun} />}
+ {selected.taskId && (
+ <TaskInstanceContent
+ instance={selected.instance}
+ task={selected.task}
+ />
+ )}
+ </Box>
+ </Flex>
+ );
+};
export default Details;
diff --git a/airflow/www/static/js/tree/index.jsx b/airflow/www/static/js/tree/index.jsx
index 2c86478..a509d25 100644
--- a/airflow/www/static/js/tree/index.jsx
+++ b/airflow/www/static/js/tree/index.jsx
@@ -38,6 +38,14 @@ const mainElement = document.getElementById('react-container');
shadowRoot.appendChild(mainElement);
const queryClient = new QueryClient();
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ refetchOnWindowFocus: false,
+ },
+ },
+});
+
function App() {
return (
<React.StrictMode>
diff --git a/airflow/www/yarn.lock b/airflow/www/yarn.lock
index 26d00cb..7da2551 100644
--- a/airflow/www/yarn.lock
+++ b/airflow/www/yarn.lock
@@ -3254,6 +3254,13 @@ axe-core@^4.0.2:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.3.tgz#b55cd8e8ddf659fe89b064680e1c6a4dceab0325"
integrity sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==
+axios@^0.26.0:
+ version "0.26.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.0.tgz#9a318f1c69ec108f8cd5f3c3d390366635e13928"
+ integrity sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==
+ dependencies:
+ follow-redirects "^1.14.8"
+
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -6018,6 +6025,11 @@ focus-lock@^0.9.1:
dependencies:
tslib "^2.0.3"
+follow-redirects@^1.14.8:
+ version "1.14.9"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
+ integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
+
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"