From dc192ab84cc795de479435b75694e305e39e3946 Mon Sep 17 00:00:00 2001 From: Jan Kadlec Date: Tue, 8 Oct 2024 15:44:07 +0200 Subject: [PATCH] fix: metric with filter failed Single metric with filter producing empty result failed with index out of bound. This should not happen and an empty dataframe with columns should be returned. JIRA: PSDK-207 risk: low --- .../gooddata_pandas/data_access.py | 8 +- .../dataframe/fixtures/filtered_empty_df.yaml | 200 ++++++++++++++++++ .../dataframe/test_not_indexed_dataframe.py | 16 +- 3 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 gooddata-pandas/tests/dataframe/fixtures/filtered_empty_df.yaml diff --git a/gooddata-pandas/gooddata_pandas/data_access.py b/gooddata-pandas/gooddata_pandas/data_access.py index cc509030e..442d00c8d 100644 --- a/gooddata-pandas/gooddata_pandas/data_access.py +++ b/gooddata-pandas/gooddata_pandas/data_access.py @@ -312,12 +312,10 @@ def _extract_for_metrics_only(response: ExecutionResponse, cols: list, col_to_me """ exec_def = response.exec_def result = response.read_result(len(exec_def.metrics)) - data = dict() + if len(result.data) == 0: + return {col: [] for col in cols} - for col in cols: - data[col] = [result.data[col_to_metric_idx[col]]] - - return data + return {col: [result.data[col_to_metric_idx[col]]] for col in cols} def _find_attribute(attributes: list[CatalogAttribute], id_obj: IdObjType) -> Union[CatalogAttribute, None]: diff --git a/gooddata-pandas/tests/dataframe/fixtures/filtered_empty_df.yaml b/gooddata-pandas/tests/dataframe/fixtures/filtered_empty_df.yaml new file mode 100644 index 000000000..9d094074c --- /dev/null +++ b/gooddata-pandas/tests/dataframe/fixtures/filtered_empty_df.yaml @@ -0,0 +1,200 @@ +# (C) 2024 GoodData Corporation +version: 1 +interactions: + - request: + method: POST + uri: http://localhost:3000/api/v1/actions/workspaces/demo/execution/afm/execute + body: + execution: + attributes: [] + filters: + - relativeDateFilter: + dataset: + identifier: + id: date + type: dataset + from: 1 + granularity: YEAR + to: 2 + measures: + - definition: + measure: + item: + identifier: + id: revenue + type: metric + computeRatio: false + filters: [] + localIdentifier: m_revenue + resultSpec: + dimensions: + - itemIdentifiers: + - measureGroup + localIdentifier: dim_0 + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + Content-Type: + - application/json + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Content-Disposition, Content-Length, Content-Range, Set-Cookie + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' *.wistia.com *.wistia.net; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' *.wistia.com *.wistia.net *.hsforms.net *.hsforms.com + src.litix.io matomo.anywhere.gooddata.com *.jquery.com unpkg.com cdnjs.cloudflare.com; + img-src * data: blob:; style-src ''self'' ''unsafe-inline'' fonts.googleapis.com + cdn.jsdelivr.net fast.fonts.net; font-src ''self'' data: fonts.gstatic.com + *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src + ''self'' *.hsforms.net *.hsforms.com; object-src ''none''; worker-src + ''self'' blob:; child-src blob:; connect-src ''self'' *.tiles.mapbox.com + *.mapbox.com *.litix.io *.wistia.com *.hsforms.net *.hsforms.com embedwistia-a.akamaihd.net + matomo.anywhere.gooddata.com; media-src ''self'' blob: data: *.wistia.com + *.wistia.net embedwistia-a.akamaihd.net' + Content-Type: + - application/json + DATE: &id001 + - PLACEHOLDER + Expires: + - '0' + GoodData-Deployment: + - aio + Permission-Policy: + - geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera + 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment + 'none'; + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - nginx + Transfer-Encoding: + - chunked + Vary: + - Origin + - Access-Control-Request-Method + - Access-Control-Request-Headers + X-Content-Type-Options: + - nosniff + X-GDC-TRACE-ID: *id001 + X-XSS-Protection: + - '0' + content-length: + - '307' + set-cookie: + - SPRING_REDIRECT_URI=; Max-Age=0; Expires=Tue, 08 Oct 2024 13:35:16 GMT; + Path=/; HTTPOnly; SameSite=Lax + body: + string: + executionResponse: + dimensions: + - headers: + - measureGroupHeaders: + - localIdentifier: m_revenue + format: $#,##0 + name: Revenue + localIdentifier: dim_0 + links: + executionResult: a8a633b8ec1bc44a3007020033d582740a2a95b7:6e97188bfff240406a2b583fe6e4065f304eb2250a658fcf497dc142a3377e80 + - request: + method: GET + uri: http://localhost:3000/api/v1/actions/workspaces/demo/execution/afm/execute/result/a8a633b8ec1bc44a3007020033d582740a2a95b7%3A6e97188bfff240406a2b583fe6e4065f304eb2250a658fcf497dc142a3377e80?offset=0&limit=1 + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Expose-Headers: + - Content-Disposition, Content-Length, Content-Range, Set-Cookie + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' *.wistia.com *.wistia.net; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' *.wistia.com *.wistia.net *.hsforms.net *.hsforms.com + src.litix.io matomo.anywhere.gooddata.com *.jquery.com unpkg.com cdnjs.cloudflare.com; + img-src * data: blob:; style-src ''self'' ''unsafe-inline'' fonts.googleapis.com + cdn.jsdelivr.net fast.fonts.net; font-src ''self'' data: fonts.gstatic.com + *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src + ''self'' *.hsforms.net *.hsforms.com; object-src ''none''; worker-src + ''self'' blob:; child-src blob:; connect-src ''self'' *.tiles.mapbox.com + *.mapbox.com *.litix.io *.wistia.com *.hsforms.net *.hsforms.com embedwistia-a.akamaihd.net + matomo.anywhere.gooddata.com; media-src ''self'' blob: data: *.wistia.com + *.wistia.net embedwistia-a.akamaihd.net' + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + GoodData-Deployment: + - aio + Permission-Policy: + - geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera + 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment + 'none'; + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - nginx + Transfer-Encoding: + - chunked + Vary: + - Origin + - Access-Control-Request-Method + - Access-Control-Request-Headers + X-Content-Type-Options: + - nosniff + X-GDC-TRACE-ID: *id001 + X-XSS-Protection: + - '0' + content-length: + - '131' + set-cookie: + - SPRING_REDIRECT_URI=; Max-Age=0; Expires=Tue, 08 Oct 2024 13:35:16 GMT; + Path=/; HTTPOnly; SameSite=Lax + body: + string: + data: [] + dimensionHeaders: + - headerGroups: + - headers: [] + grandTotals: [] + paging: + count: + - 0 + offset: + - 0 + total: + - 0 diff --git a/gooddata-pandas/tests/dataframe/test_not_indexed_dataframe.py b/gooddata-pandas/tests/dataframe/test_not_indexed_dataframe.py index 5a67638e5..4ea02d2ae 100644 --- a/gooddata-pandas/tests/dataframe/test_not_indexed_dataframe.py +++ b/gooddata-pandas/tests/dataframe/test_not_indexed_dataframe.py @@ -2,7 +2,7 @@ from pathlib import Path from gooddata_pandas import DataFrameFactory -from gooddata_sdk import PositiveAttributeFilter +from gooddata_sdk import ObjId, PositiveAttributeFilter, RelativeDateFilter, SimpleMetric from tests_support.vcrpy_utils import get_vcr gd_vcr = get_vcr() @@ -75,3 +75,17 @@ def test_empty_not_indexed_dataframe(gdf: DataFrameFactory): assert df.columns[0] == "product_name" assert df.columns[1] == "amount_of_top_customers" assert df.columns[2] == "total_revenue" + + +@gd_vcr.use_cassette(str(_fixtures_dir / "filtered_empty_df.yaml")) +def test_filter_empty_df(gdf: DataFrameFactory): + my_metric = SimpleMetric(local_id="m_revenue", item=ObjId(id="revenue", type="metric")) + my_filter = RelativeDateFilter( + dataset=ObjId(id="date", type="dataset"), + granularity="YEAR", + from_shift=1, + to_shift=2, + ) + df = gdf.not_indexed(columns=dict(my_metric=my_metric), filter_by=[my_filter]) + assert df.empty + assert df.columns[0] == "my_metric"