Skip to content

Commit d73bef2

Browse files
authored
Add Amazon Athena query results extra link (#36447)
1 parent 9e55f51 commit d73bef2

File tree

5 files changed

+99
-1
lines changed

5 files changed

+99
-1
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
from __future__ import annotations
18+
19+
from airflow.providers.amazon.aws.links.base_aws import BASE_AWS_CONSOLE_LINK, BaseAwsLink
20+
21+
22+
class AthenaQueryResultsLink(BaseAwsLink):
23+
"""Helper class for constructing Amazon Athena query results."""
24+
25+
name = "Query Results"
26+
key = "_athena_query_results"
27+
format_str = (
28+
BASE_AWS_CONSOLE_LINK + "/athena/home?region={region_name}#"
29+
"/query-editor/history/{query_execution_id}"
30+
)

airflow/providers/amazon/aws/operators/athena.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from airflow.configuration import conf
2424
from airflow.exceptions import AirflowException
2525
from airflow.providers.amazon.aws.hooks.athena import AthenaHook
26+
from airflow.providers.amazon.aws.links.athena import AthenaQueryResultsLink
2627
from airflow.providers.amazon.aws.operators.base_aws import AwsBaseOperator
2728
from airflow.providers.amazon.aws.triggers.athena import AthenaTrigger
2829
from airflow.providers.amazon.aws.utils.mixins import aws_template_fields
@@ -82,6 +83,7 @@ class AthenaOperator(AwsBaseOperator[AthenaHook]):
8283
)
8384
template_ext: Sequence[str] = (".sql",)
8485
template_fields_renderers = {"query": "sql"}
86+
operator_extra_links = (AthenaQueryResultsLink(),)
8587

8688
def __init__(
8789
self,
@@ -132,6 +134,13 @@ def execute(self, context: Context) -> str | None:
132134
self.client_request_token,
133135
self.workgroup,
134136
)
137+
AthenaQueryResultsLink.persist(
138+
context=context,
139+
operator=self,
140+
region_name=self.hook.conn_region_name,
141+
aws_partition=self.hook.conn_partition,
142+
query_execution_id=self.query_execution_id,
143+
)
135144

136145
if self.deferrable:
137146
self.defer(

airflow/providers/amazon/provider.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,7 @@ transfers:
711711
python-module: airflow.providers.amazon.aws.transfers.azure_blob_to_s3
712712

713713
extra-links:
714+
- airflow.providers.amazon.aws.links.athena.AthenaQueryResultsLink
714715
- airflow.providers.amazon.aws.links.batch.BatchJobDefinitionLink
715716
- airflow.providers.amazon.aws.links.batch.BatchJobDetailsLink
716717
- airflow.providers.amazon.aws.links.batch.BatchJobQueueLink
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
from __future__ import annotations
18+
19+
from airflow.providers.amazon.aws.links.athena import AthenaQueryResultsLink
20+
from tests.providers.amazon.aws.links.test_base_aws import BaseAwsLinksTestCase
21+
22+
23+
class TestAthenaQueryResultsLink(BaseAwsLinksTestCase):
24+
link_class = AthenaQueryResultsLink
25+
26+
def test_extra_link(self):
27+
self.assert_extra_link_url(
28+
expected_url=(
29+
"https://console.aws.amazon.com/athena/home"
30+
"?region=eu-west-1#/query-editor/history/00000000-0000-0000-0000-000000000000"
31+
),
32+
region_name="eu-west-1",
33+
aws_partition="aws",
34+
query_execution_id="00000000-0000-0000-0000-000000000000",
35+
)

tests/providers/amazon/aws/operators/test_athena.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757

5858

5959
class TestAthenaOperator:
60-
def setup_method(self):
60+
@pytest.fixture(autouse=True)
61+
def setup_test_cases(self):
6162
args = {
6263
"owner": "airflow",
6364
"start_date": DEFAULT_DATE,
@@ -77,6 +78,10 @@ def setup_method(self):
7778
**self.default_op_kwargs, output_location="s3://test_s3_bucket/", aws_conn_id=None, dag=self.dag
7879
)
7980

81+
with mock.patch("airflow.providers.amazon.aws.links.athena.AthenaQueryResultsLink.persist") as m:
82+
self.mocked_athena_result_link = m
83+
yield
84+
8085
def test_base_aws_op_attributes(self):
8186
op = AthenaOperator(**self.default_op_kwargs)
8287
assert op.hook.aws_conn_id == "aws_default"
@@ -138,6 +143,15 @@ def test_hook_run_small_success_query(self, mock_conn, mock_run_query, mock_chec
138143
)
139144
assert mock_check_query_status.call_count == 1
140145

146+
# Validate call persist Athena Query result link
147+
self.mocked_athena_result_link.assert_called_once_with(
148+
aws_partition=mock.ANY,
149+
context=mock.ANY,
150+
operator=mock.ANY,
151+
region_name=mock.ANY,
152+
query_execution_id=ATHENA_QUERY_ID,
153+
)
154+
141155
@mock.patch.object(
142156
AthenaHook,
143157
"check_query_status",
@@ -241,6 +255,15 @@ def test_is_deferred(self, mock_run_query):
241255

242256
assert isinstance(deferred.value.trigger, AthenaTrigger)
243257

258+
# Validate call persist Athena Query result link
259+
self.mocked_athena_result_link.assert_called_once_with(
260+
aws_partition=mock.ANY,
261+
context=mock.ANY,
262+
operator=mock.ANY,
263+
region_name=mock.ANY,
264+
query_execution_id=ATHENA_QUERY_ID,
265+
)
266+
244267
@mock.patch.object(AthenaHook, "region_name", new_callable=mock.PropertyMock)
245268
@mock.patch.object(AthenaHook, "get_conn")
246269
def test_operator_openlineage_data(self, mock_conn, mock_region_name):

0 commit comments

Comments
 (0)