diff --git a/airflow/www/static/js/api/useClearRun.js b/airflow/www/static/js/api/useClearRun.ts similarity index 80% rename from airflow/www/static/js/api/useClearRun.js rename to airflow/www/static/js/api/useClearRun.ts index f58fc45718a8a..eea285034a99a 100644 --- a/airflow/www/static/js/api/useClearRun.js +++ b/airflow/www/static/js/api/useClearRun.ts @@ -17,8 +17,9 @@ * under the License. */ -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import { useMutation, useQueryClient } from 'react-query'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import { useAutoRefresh } from '../context/autorefresh'; import useErrorToast from '../utils/useErrorToast'; @@ -26,21 +27,21 @@ import useErrorToast from '../utils/useErrorToast'; const csrfToken = getMetaValue('csrf_token'); const clearRunUrl = getMetaValue('dagrun_clear_url'); -export default function useClearRun(dagId, runId) { +export default function useClearRun(dagId: string, runId: string) { const queryClient = useQueryClient(); const errorToast = useErrorToast(); const { startRefresh } = useAutoRefresh(); return useMutation( ['dagRunClear', dagId, runId], - ({ confirmed = false }) => { - const params = new URLSearchParams({ + ({ confirmed = false }: { confirmed: boolean }) => { + const params = new URLSearchParamsWrapper({ csrf_token: csrfToken, confirmed, dag_id: dagId, dag_run_id: runId, }).toString(); - return axios.post(clearRunUrl, params, { + return axios.post(clearRunUrl, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, @@ -52,7 +53,7 @@ export default function useClearRun(dagId, runId) { queryClient.invalidateQueries('gridData'); startRefresh(); }, - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useClearTask.js b/airflow/www/static/js/api/useClearTask.ts similarity index 75% rename from airflow/www/static/js/api/useClearTask.js rename to airflow/www/static/js/api/useClearTask.ts index 5e774041446ee..a8fc1131aa739 100644 --- a/airflow/www/static/js/api/useClearTask.js +++ b/airflow/www/static/js/api/useClearTask.ts @@ -17,8 +17,9 @@ * under the License. */ -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import { useMutation, useQueryClient } from 'react-query'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import { useAutoRefresh } from '../context/autorefresh'; import useErrorToast from '../utils/useErrorToast'; @@ -28,7 +29,10 @@ const clearUrl = getMetaValue('clear_url'); export default function useClearTask({ dagId, runId, taskId, executionDate, -}) { +}: { dagId: string, + runId: string, + taskId: string, + executionDate: string }) { const queryClient = useQueryClient(); const errorToast = useErrorToast(); const { startRefresh } = useAutoRefresh(); @@ -37,8 +41,15 @@ export default function useClearTask({ ['clearTask', dagId, runId, taskId], ({ past, future, upstream, downstream, recursive, failed, confirmed, mapIndexes = [], - }) => { - const params = new URLSearchParams({ + }: { past: boolean, + future: boolean, + upstream: boolean, + downstream: boolean, + recursive: boolean, + failed: boolean, + confirmed: boolean, + mapIndexes: number[] }) => { + const params = new URLSearchParamsWrapper({ csrf_token: csrfToken, dag_id: dagId, dag_run_id: runId, @@ -53,11 +64,11 @@ export default function useClearTask({ only_failed: failed, }); - mapIndexes.forEach((mi) => { - params.append('map_index', mi); + mapIndexes.forEach((mi: number) => { + params.append('map_index', mi.toString()); }); - return axios.post(clearUrl, params.toString(), { + return axios.post(clearUrl, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, @@ -69,7 +80,7 @@ export default function useClearTask({ queryClient.invalidateQueries(['mappedInstances', dagId, runId, taskId]); startRefresh(); }, - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useConfirmMarkTask.js b/airflow/www/static/js/api/useConfirmMarkTask.ts similarity index 69% rename from airflow/www/static/js/api/useConfirmMarkTask.js rename to airflow/www/static/js/api/useConfirmMarkTask.ts index 68328ec7bc48f..ba654798c4235 100644 --- a/airflow/www/static/js/api/useConfirmMarkTask.js +++ b/airflow/www/static/js/api/useConfirmMarkTask.ts @@ -17,8 +17,10 @@ * under the License. */ -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import { useMutation } from 'react-query'; +import type { TaskState } from 'src/types'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import useErrorToast from '../utils/useErrorToast'; @@ -26,14 +28,20 @@ const confirmUrl = getMetaValue('confirm_url'); export default function useConfirmMarkTask({ dagId, runId, taskId, state, -}) { +}: { dagId: string, runId: string, taskId: string, state: TaskState }) { const errorToast = useErrorToast(); return useMutation( ['confirmStateChange', dagId, runId, taskId, state], ({ past, future, upstream, downstream, mapIndexes = [], + }: { + past: boolean, + future: boolean, + upstream: boolean, + downstream: boolean, + mapIndexes: number[], }) => { - const params = new URLSearchParams({ + const params = new URLSearchParamsWrapper({ dag_id: dagId, dag_run_id: runId, task_id: taskId, @@ -44,14 +52,13 @@ export default function useConfirmMarkTask({ state, }); - mapIndexes.forEach((mi) => { - params.append('map_index', mi); + mapIndexes.forEach((mi: number) => { + params.append('map_index', mi.toString()); }); - - return axios.get(confirmUrl, { params }); + return axios.get(confirmUrl, { params }); }, { - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useDataset.ts b/airflow/www/static/js/api/useDataset.ts index 6f213f5ca4a0c..900bdbc2d18b6 100644 --- a/airflow/www/static/js/api/useDataset.ts +++ b/airflow/www/static/js/api/useDataset.ts @@ -23,15 +23,11 @@ import { useQuery } from 'react-query'; import { getMetaValue } from 'src/utils'; import type { API } from 'src/types'; -interface Props { - datasetUri: string; -} - -export default function useDataset({ datasetUri }: Props) { +export default function useDataset({ uri }: API.GetDatasetVariables) { return useQuery( - ['dataset', datasetUri], + ['dataset', uri], () => { - const datasetUrl = `${getMetaValue('datasets_api') || '/api/v1/datasets'}/${encodeURIComponent(datasetUri)}`; + const datasetUrl = `${getMetaValue('datasets_api') || '/api/v1/datasets'}/${encodeURIComponent(uri)}`; return axios.get(datasetUrl); }, ); diff --git a/airflow/www/static/js/api/useDatasetEvents.ts b/airflow/www/static/js/api/useDatasetEvents.ts index 9412bceea2f35..f92899e2deb55 100644 --- a/airflow/www/static/js/api/useDatasetEvents.ts +++ b/airflow/www/static/js/api/useDatasetEvents.ts @@ -22,43 +22,28 @@ import { useQuery } from 'react-query'; import { getMetaValue } from 'src/utils'; import type { API } from 'src/types'; - -interface DatasetEventsData { - datasetEvents: API.DatasetEvent[]; - totalEntries: number; -} - -interface Props { - datasetId?: number; - dagId?: string; - taskId?: string; - runId?: string; - mapIndex?: number; - limit?: number; - offset?: number; - order?: string; -} +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; export default function useDatasetEvents({ - datasetId, dagId, runId, taskId, mapIndex, limit, offset, order, -}: Props) { + datasetId, sourceDagId, sourceRunId, sourceTaskId, sourceMapIndex, limit, offset, orderBy, +}: API.GetDatasetEventsVariables) { const query = useQuery( - ['datasets-events', datasetId, dagId, runId, taskId, mapIndex, limit, offset, order], + ['datasets-events', datasetId, sourceDagId, sourceRunId, sourceTaskId, sourceMapIndex, limit, offset, orderBy], () => { const datasetsUrl = getMetaValue('dataset_events_api') || '/api/v1/datasets/events'; - const params = new URLSearchParams(); + const params = new URLSearchParamsWrapper(); if (limit) params.set('limit', limit.toString()); if (offset) params.set('offset', offset.toString()); - if (order) params.set('order_by', order); + if (orderBy) params.set('order_by', orderBy); if (datasetId) params.set('dataset_id', datasetId.toString()); - if (dagId) params.set('source_dag_id', dagId); - if (runId) params.set('source_run_id', runId); - if (taskId) params.set('source_task_id', taskId); - if (mapIndex) params.set('source_map_index', mapIndex.toString()); + if (sourceDagId) params.set('source_dag_id', sourceDagId); + if (sourceRunId) params.set('source_run_id', sourceRunId); + if (sourceTaskId) params.set('source_task_id', sourceTaskId); + if (sourceMapIndex) params.set('source_map_index', sourceMapIndex.toString()); - return axios.get(datasetsUrl, { + return axios.get(datasetsUrl, { params, }); }, diff --git a/airflow/www/static/js/api/useExtraLinks.js b/airflow/www/static/js/api/useExtraLinks.ts similarity index 86% rename from airflow/www/static/js/api/useExtraLinks.js rename to airflow/www/static/js/api/useExtraLinks.ts index 24262b6b7cb6e..76fe4610bbf46 100644 --- a/airflow/www/static/js/api/useExtraLinks.js +++ b/airflow/www/static/js/api/useExtraLinks.ts @@ -17,14 +17,21 @@ * under the License. */ -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import { useQuery } from 'react-query'; import { getMetaValue } from '../utils'; const extraLinksUrl = getMetaValue('extra_links_url'); +interface LinkData { + url: string | null; + error: string | null; +} + export default function useExtraLinks({ dagId, taskId, executionDate, extraLinks, +}: { + dagId: string, taskId: string, executionDate: string, extraLinks: string[], }) { return useQuery( ['extraLinks', dagId, taskId, executionDate], @@ -36,7 +43,7 @@ export default function useExtraLinks({ }&execution_date=${encodeURIComponent(executionDate) }&link_name=${encodeURIComponent(link)}`; try { - const datum = await axios.get(url); + const datum = await axios.get(url); return { name: link, url: datum.url, diff --git a/airflow/www/static/js/api/useGridData.test.js b/airflow/www/static/js/api/useGridData.test.ts similarity index 65% rename from airflow/www/static/js/api/useGridData.test.js rename to airflow/www/static/js/api/useGridData.test.ts index 83fabe001891c..c5a0e6c9de515 100644 --- a/airflow/www/static/js/api/useGridData.test.js +++ b/airflow/www/static/js/api/useGridData.test.ts @@ -19,42 +19,53 @@ /* global describe, test, expect */ +import type { DagRun } from 'src/types'; import { areActiveRuns } from './useGridData'; +const commonDagRunParams = { + runId: 'runId', + executionDate: '2022-01-01T10:00+00:00', + dataIntervalStart: '2022-01-01T05:00+00:00', + dataIntervalEnd: '2022-01-01T10:00+00:00', + startDate: null, + endDate: null, + lastSchedulingDecision: null, +}; + describe('Test areActiveRuns()', () => { test('Correctly detects active runs', () => { - const runs = [ - { runType: 'scheduled', state: 'success' }, - { runType: 'manual', state: 'queued' }, + const runs: DagRun[] = [ + { runType: 'scheduled', state: 'success', ...commonDagRunParams }, + { runType: 'manual', state: 'queued', ...commonDagRunParams }, ]; expect(areActiveRuns(runs)).toBe(true); }); test('Returns false when all runs are resolved', () => { - const runs = [ - { runType: 'scheduled', state: 'success' }, - { runType: 'manual', state: 'failed' }, - { runType: 'manual', state: 'failed' }, + const runs: DagRun[] = [ + { runType: 'scheduled', state: 'success', ...commonDagRunParams }, + { runType: 'manual', state: 'failed', ...commonDagRunParams }, + { runType: 'manual', state: 'failed', ...commonDagRunParams }, ]; const result = areActiveRuns(runs); expect(result).toBe(false); }); - test('Returns false when filtering runs runtype ["backfill"] and state ["failed"]', () => { - const runs = [ - { runType: 'scheduled', state: 'success' }, - { runType: 'manual', state: 'failed' }, - { runType: 'backfill', state: 'failed' }, + test('Returns false when filtering runs runtype ["backfill"]', () => { + const runs: DagRun[] = [ + { runType: 'scheduled', state: 'success', ...commonDagRunParams }, + { runType: 'manual', state: 'failed', ...commonDagRunParams }, + { runType: 'backfill', state: 'failed', ...commonDagRunParams }, ]; const result = areActiveRuns(runs); expect(result).toBe(false); }); test('Returns false when filtering runs runtype ["backfill"] and state ["queued"]', () => { - const runs = [ - { runType: 'scheduled', state: 'success' }, - { runType: 'manual', state: 'failed' }, - { runType: 'backfill', state: 'queued' }, + const runs: DagRun[] = [ + { runType: 'scheduled', state: 'success', ...commonDagRunParams }, + { runType: 'manual', state: 'failed', ...commonDagRunParams }, + { runType: 'backfill', state: 'queued', ...commonDagRunParams }, ]; const result = areActiveRuns(runs); expect(result).toBe(false); @@ -87,7 +98,7 @@ describe('Test areActiveRuns()', () => { }, ].forEach(({ state, runType, expectedResult }) => { test(`Returns ${expectedResult} when filtering runs with runtype ["${runType}"] and state ["${state}"]`, () => { - const runs = [{ runType, state }]; + const runs: DagRun[] = [{ runType, state, ...commonDagRunParams } as DagRun]; const result = areActiveRuns(runs); expect(result).toBe(expectedResult); }); diff --git a/airflow/www/static/js/api/useMappedInstances.ts b/airflow/www/static/js/api/useMappedInstances.ts index 4ddefccf9fff9..c14557a03ad90 100644 --- a/airflow/www/static/js/api/useMappedInstances.ts +++ b/airflow/www/static/js/api/useMappedInstances.ts @@ -22,33 +22,19 @@ import { useQuery } from 'react-query'; import { getMetaValue } from 'src/utils'; import { useAutoRefresh } from 'src/context/autorefresh'; -import type { API, DagRun } from 'src/types'; +import type { API } from 'src/types'; const mappedInstancesUrl = getMetaValue('mapped_instances_api'); -interface MappedInstanceData { - taskInstances: API.TaskInstance[]; - totalEntries: number; -} - -interface Props { - dagId: string; - runId: DagRun['runId']; - taskId: string; - limit?: number; - offset?: number; - order?: string; -} - export default function useMappedInstances({ - dagId, runId, taskId, limit, offset, order, -}: Props) { - const url = mappedInstancesUrl.replace('_DAG_RUN_ID_', runId).replace('_TASK_ID_', taskId); - const orderParam = order && order !== 'map_index' ? { order_by: order } : {}; + dagId, dagRunId, taskId, limit, offset, orderBy, +}: API.GetMappedTaskInstancesVariables) { + const url = mappedInstancesUrl.replace('_DAG_RUN_ID_', dagRunId).replace('_TASK_ID_', taskId); + const orderParam = orderBy && orderBy !== 'map_index' ? { order_by: orderBy } : {}; const { isRefreshOn } = useAutoRefresh(); return useQuery( - ['mappedInstances', dagId, runId, taskId, offset, order], - () => axios.get(url, { + ['mappedInstances', dagId, dagRunId, taskId, offset, orderBy], + () => axios.get(url, { params: { offset, limit, ...orderParam }, }), { diff --git a/airflow/www/static/js/api/useMarkFailedRun.js b/airflow/www/static/js/api/useMarkFailedRun.ts similarity index 85% rename from airflow/www/static/js/api/useMarkFailedRun.js rename to airflow/www/static/js/api/useMarkFailedRun.ts index 8e37bc4c1afdb..ac5ef6def44ba 100644 --- a/airflow/www/static/js/api/useMarkFailedRun.js +++ b/airflow/www/static/js/api/useMarkFailedRun.ts @@ -19,6 +19,7 @@ import axios from 'axios'; import { useMutation, useQueryClient } from 'react-query'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import { useAutoRefresh } from '../context/autorefresh'; import useErrorToast from '../utils/useErrorToast'; @@ -26,14 +27,14 @@ import useErrorToast from '../utils/useErrorToast'; const csrfToken = getMetaValue('csrf_token'); const markFailedUrl = getMetaValue('dagrun_failed_url'); -export default function useMarkFailedRun(dagId, runId) { +export default function useMarkFailedRun(dagId: string, runId: string) { const queryClient = useQueryClient(); const errorToast = useErrorToast(); const { startRefresh } = useAutoRefresh(); return useMutation( ['dagRunFailed', dagId, runId], - ({ confirmed = false }) => { - const params = new URLSearchParams({ + ({ confirmed = false }: { confirmed: boolean }) => { + const params = new URLSearchParamsWrapper({ csrf_token: csrfToken, confirmed, dag_id: dagId, @@ -51,7 +52,7 @@ export default function useMarkFailedRun(dagId, runId) { queryClient.invalidateQueries('gridData'); startRefresh(); }, - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useMarkFailedTask.js b/airflow/www/static/js/api/useMarkFailedTask.ts similarity index 82% rename from airflow/www/static/js/api/useMarkFailedTask.js rename to airflow/www/static/js/api/useMarkFailedTask.ts index 4ba8b601f44f5..caac2fcd9d27b 100644 --- a/airflow/www/static/js/api/useMarkFailedTask.js +++ b/airflow/www/static/js/api/useMarkFailedTask.ts @@ -19,6 +19,7 @@ import axios from 'axios'; import { useMutation, useQueryClient } from 'react-query'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import { useAutoRefresh } from '../context/autorefresh'; import useErrorToast from '../utils/useErrorToast'; @@ -28,6 +29,8 @@ const csrfToken = getMetaValue('csrf_token'); export default function useMarkFailedTask({ dagId, runId, taskId, +}: { + dagId: string, runId: string, taskId: string, }) { const queryClient = useQueryClient(); const errorToast = useErrorToast(); @@ -36,8 +39,14 @@ export default function useMarkFailedTask({ ['markFailed', dagId, runId, taskId], ({ past, future, upstream, downstream, mapIndexes = [], + }: { + past: boolean, + future: boolean, + upstream: boolean, + downstream: boolean, + mapIndexes: number[] }) => { - const params = new URLSearchParams({ + const params = new URLSearchParamsWrapper({ csrf_token: csrfToken, dag_id: dagId, dag_run_id: runId, @@ -49,8 +58,8 @@ export default function useMarkFailedTask({ downstream, }); - mapIndexes.forEach((mi) => { - params.append('map_index', mi); + mapIndexes.forEach((mi: number) => { + params.append('map_index', mi.toString()); }); return axios.post(failedUrl, params.toString(), { @@ -65,7 +74,7 @@ export default function useMarkFailedTask({ queryClient.invalidateQueries(['mappedInstances', dagId, runId, taskId]); startRefresh(); }, - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useMarkSuccessRun.js b/airflow/www/static/js/api/useMarkSuccessRun.ts similarity index 85% rename from airflow/www/static/js/api/useMarkSuccessRun.js rename to airflow/www/static/js/api/useMarkSuccessRun.ts index 7b3cb77665ee0..d90bbb03a8d1b 100644 --- a/airflow/www/static/js/api/useMarkSuccessRun.js +++ b/airflow/www/static/js/api/useMarkSuccessRun.ts @@ -19,6 +19,7 @@ import axios from 'axios'; import { useMutation, useQueryClient } from 'react-query'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import { useAutoRefresh } from '../context/autorefresh'; import useErrorToast from '../utils/useErrorToast'; @@ -26,14 +27,14 @@ import useErrorToast from '../utils/useErrorToast'; const markSuccessUrl = getMetaValue('dagrun_success_url'); const csrfToken = getMetaValue('csrf_token'); -export default function useMarkSuccessRun(dagId, runId) { +export default function useMarkSuccessRun(dagId: string, runId: string) { const queryClient = useQueryClient(); const errorToast = useErrorToast(); const { startRefresh } = useAutoRefresh(); return useMutation( ['dagRunSuccess', dagId, runId], - ({ confirmed = false }) => { - const params = new URLSearchParams({ + ({ confirmed = false }: { confirmed: boolean }) => { + const params = new URLSearchParamsWrapper({ csrf_token: csrfToken, confirmed, dag_id: dagId, @@ -51,7 +52,7 @@ export default function useMarkSuccessRun(dagId, runId) { queryClient.invalidateQueries('gridData'); startRefresh(); }, - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useMarkSuccessTask.js b/airflow/www/static/js/api/useMarkSuccessTask.ts similarity index 82% rename from airflow/www/static/js/api/useMarkSuccessTask.js rename to airflow/www/static/js/api/useMarkSuccessTask.ts index b3415a11468e9..8120b58edfa63 100644 --- a/airflow/www/static/js/api/useMarkSuccessTask.js +++ b/airflow/www/static/js/api/useMarkSuccessTask.ts @@ -19,6 +19,7 @@ import axios from 'axios'; import { useMutation, useQueryClient } from 'react-query'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import { useAutoRefresh } from '../context/autorefresh'; import useErrorToast from '../utils/useErrorToast'; @@ -28,6 +29,8 @@ const successUrl = getMetaValue('success_url'); export default function useMarkSuccessTask({ dagId, runId, taskId, +}: { + dagId: string, runId: string, taskId: string, }) { const queryClient = useQueryClient(); const errorToast = useErrorToast(); @@ -36,8 +39,14 @@ export default function useMarkSuccessTask({ ['markSuccess', dagId, runId, taskId], ({ past, future, upstream, downstream, mapIndexes = [], + }: { + past: boolean, + future: boolean, + upstream: boolean, + downstream: boolean, + mapIndexes: number[] }) => { - const params = new URLSearchParams({ + const params = new URLSearchParamsWrapper({ csrf_token: csrfToken, dag_id: dagId, dag_run_id: runId, @@ -47,11 +56,10 @@ export default function useMarkSuccessTask({ future, upstream, downstream, - map_indexes: mapIndexes, }); - mapIndexes.forEach((mi) => { - params.append('map_index', mi); + mapIndexes.forEach((mi: number) => { + params.append('map_index', mi.toString()); }); return axios.post(successUrl, params.toString(), { @@ -66,7 +74,7 @@ export default function useMarkSuccessTask({ queryClient.invalidateQueries(['mappedInstances', dagId, runId, taskId]); startRefresh(); }, - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useQueueRun.js b/airflow/www/static/js/api/useQueueRun.ts similarity index 85% rename from airflow/www/static/js/api/useQueueRun.js rename to airflow/www/static/js/api/useQueueRun.ts index 5ab3879d0791f..e919c3f60fc49 100644 --- a/airflow/www/static/js/api/useQueueRun.js +++ b/airflow/www/static/js/api/useQueueRun.ts @@ -19,6 +19,7 @@ import axios from 'axios'; import { useMutation, useQueryClient } from 'react-query'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import { useAutoRefresh } from '../context/autorefresh'; import useErrorToast from '../utils/useErrorToast'; @@ -26,20 +27,19 @@ import useErrorToast from '../utils/useErrorToast'; const csrfToken = getMetaValue('csrf_token'); const queuedUrl = getMetaValue('dagrun_queued_url'); -export default function useQueueRun(dagId, runId) { +export default function useQueueRun(dagId: string, runId: string) { const queryClient = useQueryClient(); const errorToast = useErrorToast(); const { startRefresh } = useAutoRefresh(); return useMutation( ['dagRunQueue', dagId, runId], - ({ confirmed = false }) => { - const params = new URLSearchParams({ + ({ confirmed = false }: { confirmed: boolean }) => { + const params = new URLSearchParamsWrapper({ csrf_token: csrfToken, confirmed, dag_id: dagId, dag_run_id: runId, }).toString(); - return axios.post(queuedUrl, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -51,7 +51,7 @@ export default function useQueueRun(dagId, runId) { queryClient.invalidateQueries('gridData'); startRefresh(); }, - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useRunTask.js b/airflow/www/static/js/api/useRunTask.ts similarity index 84% rename from airflow/www/static/js/api/useRunTask.js rename to airflow/www/static/js/api/useRunTask.ts index 1235acd41cf5d..98b3cc873cb10 100644 --- a/airflow/www/static/js/api/useRunTask.js +++ b/airflow/www/static/js/api/useRunTask.ts @@ -19,6 +19,7 @@ import axios from 'axios'; import { useMutation, useQueryClient } from 'react-query'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import { getMetaValue } from '../utils'; import { useAutoRefresh } from '../context/autorefresh'; import useErrorToast from '../utils/useErrorToast'; @@ -26,7 +27,7 @@ import useErrorToast from '../utils/useErrorToast'; const csrfToken = getMetaValue('csrf_token'); const runUrl = getMetaValue('run_url'); -export default function useRunTask(dagId, runId, taskId) { +export default function useRunTask(dagId: string, runId: string, taskId: string) { const queryClient = useQueryClient(); const errorToast = useErrorToast(); const { startRefresh } = useAutoRefresh(); @@ -37,9 +38,14 @@ export default function useRunTask(dagId, runId, taskId) { ignoreTaskState, ignoreTaskDeps, mapIndexes, + }:{ + ignoreAllDeps: boolean, + ignoreTaskState: boolean, + ignoreTaskDeps: boolean, + mapIndexes: number[], }) => Promise.all( (mapIndexes.length ? mapIndexes : [-1]).map((mi) => { - const params = new URLSearchParams({ + const params = new URLSearchParamsWrapper({ csrf_token: csrfToken, dag_id: dagId, dag_run_id: runId, @@ -63,7 +69,7 @@ export default function useRunTask(dagId, runId, taskId) { queryClient.invalidateQueries(['mappedInstances', dagId, runId, taskId]); startRefresh(); }, - onError: (error) => errorToast({ error }), + onError: (error: Error) => errorToast({ error }), }, ); } diff --git a/airflow/www/static/js/api/useTaskInstance.tsx b/airflow/www/static/js/api/useTaskInstance.ts similarity index 100% rename from airflow/www/static/js/api/useTaskInstance.tsx rename to airflow/www/static/js/api/useTaskInstance.ts diff --git a/airflow/www/static/js/api/useTaskLog.tsx b/airflow/www/static/js/api/useTaskLog.ts similarity index 92% rename from airflow/www/static/js/api/useTaskLog.tsx rename to airflow/www/static/js/api/useTaskLog.ts index f389f4c11831d..1c8b999de81cf 100644 --- a/airflow/www/static/js/api/useTaskLog.tsx +++ b/airflow/www/static/js/api/useTaskLog.ts @@ -20,6 +20,7 @@ import axios, { AxiosResponse } from 'axios'; import { useQuery } from 'react-query'; import { useAutoRefresh } from 'src/context/autorefresh'; +import type { API } from 'src/types'; import { getMetaValue } from 'src/utils'; @@ -27,14 +28,7 @@ const taskLogApi = getMetaValue('task_log_api'); const useTaskLog = ({ dagId, dagRunId, taskId, taskTryNumber, mapIndex, fullContent, -}: { - dagId: string, - dagRunId: string, - taskId: string, - taskTryNumber: number, - mapIndex?: number, - fullContent: boolean, -}) => { +}: API.GetLogVariables) => { let url: string = ''; if (taskLogApi) { url = taskLogApi.replace('_DAG_RUN_ID_', dagRunId).replace('_TASK_ID_', taskId).replace(/-1$/, taskTryNumber.toString()); diff --git a/airflow/www/static/js/api/useUpstreamDatasetEvents.ts b/airflow/www/static/js/api/useUpstreamDatasetEvents.ts index 4821b4dd5ff36..4fa1b765175fb 100644 --- a/airflow/www/static/js/api/useUpstreamDatasetEvents.ts +++ b/airflow/www/static/js/api/useUpstreamDatasetEvents.ts @@ -27,11 +27,6 @@ interface Props { runId: string; } -interface UpstreamEventsData { - datasetEvents: API.DatasetEvent[]; - totalEntries: number; -} - export default function useUpstreamDatasetEvents({ runId }: Props) { const query = useQuery( ['upstreamDatasetEvents', runId], @@ -41,7 +36,7 @@ export default function useUpstreamDatasetEvents({ runId }: Props) { getMetaValue('upstream_dataset_events_api') || `api/v1/dags/${dagId}/dagRuns/_DAG_RUN_ID_/upstreamDatasetEvents` ).replace('_DAG_RUN_ID_', runId); - return axios.get(upstreamEventsUrl); + return axios.get(upstreamEventsUrl); }, ); return { diff --git a/airflow/www/static/js/dag/details/dagRun/DatasetTriggerEvents.tsx b/airflow/www/static/js/dag/details/dagRun/DatasetTriggerEvents.tsx index 81ed63676a739..c18bddcfb68a1 100644 --- a/airflow/www/static/js/dag/details/dagRun/DatasetTriggerEvents.tsx +++ b/airflow/www/static/js/dag/details/dagRun/DatasetTriggerEvents.tsx @@ -32,7 +32,7 @@ interface Props { } const DatasetTriggerEvents = ({ runId }: Props) => { - const { data: { datasetEvents }, isLoading } = useUpstreamDatasetEvents({ runId }); + const { data: { datasetEvents = [] }, isLoading } = useUpstreamDatasetEvents({ runId }); const columns = useMemo( () => [ diff --git a/airflow/www/static/js/dag/details/dagRun/index.tsx b/airflow/www/static/js/dag/details/dagRun/index.tsx index b9a1056e21645..935a08c64bc22 100644 --- a/airflow/www/static/js/dag/details/dagRun/index.tsx +++ b/airflow/www/static/js/dag/details/dagRun/index.tsx @@ -41,6 +41,7 @@ import { formatDuration, getDuration } from 'src/datetime_utils'; import Time from 'src/components/Time'; import RunTypeIcon from 'src/components/RunTypeIcon'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import MarkFailedRun from './MarkFailedRun'; import MarkSuccessRun from './MarkSuccessRun'; import QueueRun from './QueueRun'; @@ -69,10 +70,10 @@ const DagRun = ({ runId }: Props) => { startDate, endDate, } = run; - const detailsParams = new URLSearchParams({ + const detailsParams = new URLSearchParamsWrapper({ run_id: runId, }).toString(); - const graphParams = new URLSearchParams({ + const graphParams = new URLSearchParamsWrapper({ execution_date: executionDate, }).toString(); const graphLink = appendSearchParams(graphUrl, graphParams); diff --git a/airflow/www/static/js/dag/details/taskInstance/DatasetUpdateEvents.tsx b/airflow/www/static/js/dag/details/taskInstance/DatasetUpdateEvents.tsx index 8cc8fb7aee2dd..885819b2362a4 100644 --- a/airflow/www/static/js/dag/details/taskInstance/DatasetUpdateEvents.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/DatasetUpdateEvents.tsx @@ -36,7 +36,13 @@ interface Props { const dagId = getMetaValue('dag_id') || undefined; const DatasetUpdateEvents = ({ runId, taskId }: Props) => { - const { data: { datasetEvents }, isLoading } = useDatasetEvents({ runId, taskId, dagId }); + const { data: { datasetEvents = [] }, isLoading } = useDatasetEvents( + { + sourceDagId: dagId, + sourceRunId: runId, + sourceTaskId: taskId, + }, + ); const columns = useMemo( () => [ diff --git a/airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx b/airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx index 580d8c9bca87f..2c9dbfe303940 100644 --- a/airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx @@ -26,11 +26,10 @@ import { } from '@chakra-ui/react'; import { useExtraLinks } from 'src/api'; -import type { Task } from 'src/types'; interface Props { dagId: string; - taskId: Task['id']; + taskId: string; executionDate: string; extraLinks: string[]; } @@ -46,7 +45,7 @@ const ExtraLinks = ({ }); if (!links.length) return null; - const isExternal = (url: string) => /^(?:[a-z]+:)?\/\//.test(url); + const isExternal = (url: string | null) => url && /^(?:[a-z]+:)?\/\//.test(url); return ( <> diff --git a/airflow/www/static/js/dag/details/taskInstance/Logs/index.tsx b/airflow/www/static/js/dag/details/taskInstance/Logs/index.tsx index d6e8c5fcac71d..a5324f2c88287 100644 --- a/airflow/www/static/js/dag/details/taskInstance/Logs/index.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/Logs/index.tsx @@ -37,6 +37,7 @@ import { useTimezone } from 'src/context/timezone'; import type { Dag, DagRun, TaskInstance } from 'src/types'; import MultiSelect from 'src/components/MultiSelect'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; import LogLink from './LogLink'; import { LogLevel, logLevelColorMapping, parseLogs } from './utils'; @@ -113,7 +114,7 @@ const Logs = ({ fullContent: shouldRequestFullContent, }); - const params = new URLSearchParams({ + const params = new URLSearchParamsWrapper({ task_id: taskId, execution_date: executionDate, }); diff --git a/airflow/www/static/js/dag/details/taskInstance/MappedInstances.tsx b/airflow/www/static/js/dag/details/taskInstance/MappedInstances.tsx index 9796814fcc8be..fc32a9844b55a 100644 --- a/airflow/www/static/js/dag/details/taskInstance/MappedInstances.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/MappedInstances.tsx @@ -50,13 +50,13 @@ const MappedInstances = ({ const sort = sortBy[0]; - const order = sort && (sort.id === 'state' || sort.id === 'mapIndex') ? `${sort.desc ? '-' : ''}${snakeCase(sort.id)}` : ''; + const orderBy = sort && (sort.id === 'state' || sort.id === 'mapIndex') ? `${sort.desc ? '-' : ''}${snakeCase(sort.id)}` : ''; const { - data: { taskInstances, totalEntries } = { taskInstances: [], totalEntries: 0 }, + data: { taskInstances = [], totalEntries = 0 } = { taskInstances: [], totalEntries: 0 }, isLoading, } = useMappedInstances({ - dagId, runId, taskId, limit, offset, order, + dagId, dagRunId: runId, taskId, limit, offset, orderBy, }); const data = useMemo(() => taskInstances.map((mi) => ({ diff --git a/airflow/www/static/js/dag/details/taskInstance/Nav.tsx b/airflow/www/static/js/dag/details/taskInstance/Nav.tsx index 6a2c314930867..833a7b3169742 100644 --- a/airflow/www/static/js/dag/details/taskInstance/Nav.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/Nav.tsx @@ -26,6 +26,7 @@ import { import { getMetaValue, appendSearchParams } from 'src/utils'; import LinkButton from 'src/components/LinkButton'; import type { Task, DagRun } from 'src/types'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; const dagId = getMetaValue('dag_id'); const isK8sExecutor = getMetaValue('k8s_or_k8scelery_executor') === 'True'; @@ -53,26 +54,26 @@ const Nav = ({ runId, taskId, executionDate, operator, isMapped = false, mapIndex, }: Props) => { if (!taskId) return null; - const params = new URLSearchParams({ + const params = new URLSearchParamsWrapper({ task_id: taskId, execution_date: executionDate, - map_index: mapIndex?.toString() ?? '-1', + map_index: mapIndex ?? -1, }); const detailsLink = `${taskUrl}&${params}`; const renderedLink = `${renderedTemplatesUrl}&${params}`; const logLink = `${logUrl}&${params}`; const xcomLink = `${xcomUrl}&${params}`; const k8sLink = `${renderedK8sUrl}&${params}`; - const listParams = new URLSearchParams({ + const listParams = new URLSearchParamsWrapper({ _flt_3_dag_id: dagId, _flt_3_task_id: taskId, _oc_TaskInstanceModelView: 'dag_run.execution_date', }); - const subDagParams = new URLSearchParams({ + const subDagParams = new URLSearchParamsWrapper({ execution_date: executionDate, }).toString(); - const filterParams = new URLSearchParams({ + const filterParams = new URLSearchParamsWrapper({ task_id: taskId, dag_run_id: runId, root: taskId, diff --git a/airflow/www/static/js/dag/useFilters.tsx b/airflow/www/static/js/dag/useFilters.tsx index 8846f32587d9e..7e7fe2e04d2a1 100644 --- a/airflow/www/static/js/dag/useFilters.tsx +++ b/airflow/www/static/js/dag/useFilters.tsx @@ -20,6 +20,7 @@ /* global moment */ import { useSearchParams } from 'react-router-dom'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; declare const defaultDagRunDisplayNumber: number; @@ -66,7 +67,7 @@ const useFilters = (): FilterHookReturn => { formatFn?: (arg: string) => string, ) => (value: string) => { const formattedValue = formatFn ? formatFn(value) : value; - const params = new URLSearchParams(searchParams); + const params = new URLSearchParamsWrapper(searchParams); if (formattedValue) params.set(paramName, formattedValue); else params.delete(paramName); diff --git a/airflow/www/static/js/dag/useSelection.ts b/airflow/www/static/js/dag/useSelection.ts index 790e584e83ae0..6242973b497a2 100644 --- a/airflow/www/static/js/dag/useSelection.ts +++ b/airflow/www/static/js/dag/useSelection.ts @@ -18,6 +18,7 @@ */ import { useSearchParams } from 'react-router-dom'; +import URLSearchParamsWrapper from 'src/utils/URLSearchParamWrapper'; const RUN_ID = 'dag_run_id'; const TASK_ID = 'task_id'; @@ -41,7 +42,7 @@ const useSelection = () => { }; const onSelect = ({ runId, taskId, mapIndex }: SelectionProps) => { - const params = new URLSearchParams(searchParams); + const params = new URLSearchParamsWrapper(searchParams); if (runId) params.set(RUN_ID, runId); else params.delete(RUN_ID); diff --git a/airflow/www/static/js/datasets/DatasetEvents.tsx b/airflow/www/static/js/datasets/DatasetEvents.tsx index 384aebd67ed4c..0a236ad0dbc0a 100644 --- a/airflow/www/static/js/datasets/DatasetEvents.tsx +++ b/airflow/www/static/js/datasets/DatasetEvents.tsx @@ -34,13 +34,13 @@ const Events = ({ const [sortBy, setSortBy] = useState[]>([{ id: 'timestamp', desc: true }]); const sort = sortBy[0]; - const order = sort ? `${sort.desc ? '-' : ''}${snakeCase(sort.id)}` : ''; + const orderBy = sort ? `${sort.desc ? '-' : ''}${snakeCase(sort.id)}` : ''; const { - data: { datasetEvents, totalEntries }, + data: { datasetEvents = [], totalEntries = 0 }, isLoading: isEventsLoading, } = useDatasetEvents({ - datasetId, limit, offset, order, + datasetId, limit, offset, orderBy, }); const columns = useMemo( diff --git a/airflow/www/static/js/datasets/Details.tsx b/airflow/www/static/js/datasets/Details.tsx index bf85f8e2642d3..a08044438db40 100644 --- a/airflow/www/static/js/datasets/Details.tsx +++ b/airflow/www/static/js/datasets/Details.tsx @@ -28,12 +28,12 @@ import InfoTooltip from 'src/components/InfoTooltip'; import Events from './DatasetEvents'; interface Props { - datasetUri: string; + uri: string; onBack: () => void; } -const DatasetDetails = ({ datasetUri, onBack }: Props) => { - const { data: dataset, isLoading } = useDataset({ datasetUri }); +const DatasetDetails = ({ uri, onBack }: Props) => { + const { data: dataset, isLoading } = useDataset({ uri }); return ( @@ -42,8 +42,8 @@ const DatasetDetails = ({ datasetUri, onBack }: Props) => { Dataset: {' '} - {datasetUri} - + {uri} + diff --git a/airflow/www/static/js/datasets/index.tsx b/airflow/www/static/js/datasets/index.tsx index 644024c9a5f73..6e32995e926b2 100644 --- a/airflow/www/static/js/datasets/index.tsx +++ b/airflow/www/static/js/datasets/index.tsx @@ -61,7 +61,7 @@ const Datasets = () => { {datasetUri - ? + ? : } diff --git a/airflow/www/static/js/utils/URLSearchParamWrapper.ts b/airflow/www/static/js/utils/URLSearchParamWrapper.ts new file mode 100644 index 0000000000000..19dbdc6546c65 --- /dev/null +++ b/airflow/www/static/js/utils/URLSearchParamWrapper.ts @@ -0,0 +1,34 @@ +/*! + * 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. + */ + +class URLSearchParamsWrapper extends URLSearchParams { + constructor(init?: { [keys: string]: any }) { + if (init) { + const stringValues: { [keys: string]: string } = {}; + Object.keys(init).forEach( + (key) => { stringValues[key] = init[key].toString(); }, + ); + super(init); + return; + } + super(init); + } +} + +export default URLSearchParamsWrapper;