Skip to content

Commit 175c76d

Browse files
committed
fix: first working
1 parent b4be3cb commit 175c76d

File tree

1 file changed

+62
-52
lines changed

1 file changed

+62
-52
lines changed

redash/query_runner/powerbi.py

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import logging
33
from typing import Optional, Tuple
44

5+
import yaml
6+
57
from redash.query_runner import (
68
TYPE_BOOLEAN,
79
TYPE_DATETIME,
@@ -40,58 +42,59 @@
4042

4143

4244
class PowerBIDAX(BaseHTTPQueryRunner):
43-
noop_query = """
44-
EVALUATE
45-
DATATABLE(
46-
"Name", STRING, "Region", STRING,
47-
{
48-
{"User1", "East"},
49-
{"User2", "East"},
50-
{"User3", "West"},
51-
{"User4", "West"},
52-
{"User4", "East"}
53-
}
54-
)
45+
noop_query = """# yaml
46+
group_id:
47+
dataset_id:
48+
query: |
49+
EVALUATE
50+
DATATABLE(
51+
"Name", STRING, "Region", STRING,
52+
{
53+
{"User1", "East"},
54+
{"User2", "East"},
55+
{"User3", "West"},
56+
{"User4", "West"},
57+
{"User4", "East"}
58+
}
59+
)
5560
"""
61+
should_annotate_query = False
5662
response_error = "Power BI returned unexpected status code"
5763
client_id_title = "Client ID"
5864
authority_url_title = "Authority URL"
59-
scope_title = "Scope"
60-
# client_id = "Enter_the_Application_Id_here"
61-
# authority_url = 'https://login.microsoftonline.com/yourdomain.com'
62-
scope = ["https://analysis.windows.net/powerbi/api/.default"]
65+
scopes_title = "Scopes"
6366

6467
requires_authentication = True
6568
requires_url = False
6669
url_title = "Power BI URL"
67-
url = "https://api.powerbi.com/v1.0/myorg"
68-
# should_annotate_query = False
6970
username_title = "Username"
7071
password_title = "Password"
72+
default_url = "https://api.powerbi.com/v1.0/myorg"
73+
default_scopes = '["https://analysis.windows.net/powerbi/api/.default"]'
7174

7275
@classmethod
7376
def configuration_schema(cls):
7477
schema = super().configuration_schema()
7578
properties: dict = schema["properties"]
79+
properties["url"].update({"default": cls.default_url})
7680
properties.update(
7781
{
7882
"client_id": {"type": "string", "title": cls.client_id_title},
7983
"authority_url": {
8084
"type": "string",
8185
"title": cls.authority_url_title,
82-
"default": "https://login.microsoftonline.com/",
86+
"default": "https://login.microsoftonline.com/<tenant name/yourdomain.com>",
8387
},
84-
"scope": {
85-
"type": "list",
86-
"title": cls.scope_title,
87-
# "default": ["https://analysis.windows.net/powerbi/api/.default"],
88+
"scopes": {
89+
"type": "string",
90+
"title": cls.scopes_title,
91+
"default": cls.default_scopes,
8892
},
8993
}
9094
)
9195
schema["required"] = schema.get("required", []) + [
9296
"client_id",
9397
"authority_url",
94-
"scope",
9598
]
9699
return schema
97100

@@ -106,61 +109,67 @@ def enabled(cls):
106109
def __init__(self, *args, **kwargs):
107110
super().__init__(*args, **kwargs)
108111
self.syntax = "yaml"
112+
self.configuration["url"] = self.configuration.get("url", self.default_url)
113+
scopes = self.configuration.get("scopes", self.default_scopes)
114+
self.configuration["scopes"] = scopes
115+
self.configuration["scopes_array"] = json_loads(scopes)
109116

110117
def test_connection(self):
111118
_, error = self.get_response("/availableFeatures")
112119
if error is not None:
113120
raise Exception(error)
114121

115122
def get_auth(self):
123+
return None
124+
125+
def get_authorization(self):
116126
client_id = self.configuration["client_id"]
117127
authority_url = self.configuration["authority_url"]
118-
scope = self.configuration["scope"]
119-
# username = self.configuration["username"]
120-
# password = self.configuration["password"]
128+
scopes = self.configuration["scopes_array"]
121129
username, password = super().get_auth()
122130
app = msal.PublicClientApplication(client_id=client_id, authority=authority_url)
123131
result = app.acquire_token_by_username_password(
124132
username=username,
125133
password=password,
126-
scopes=scope,
134+
scopes=scopes,
127135
)
128136
access_token = result["access_token"]
129137
return f"Bearer {access_token}"
130138

131-
def get_response(self, url, auth=None, http_method="get", **kwargs):
139+
def get_response(self, url: str, auth=None, http_method="get", **kwargs):
132140
url = "{}{}".format(self.configuration["url"], url)
133141
headers = kwargs.pop("headers", {})
134142
headers["Accept"] = "application/json"
135143
headers["Content-Type"] = "application/json"
136-
# access_token = self._get_access_token()
137-
# headers["Authorization"] = f"Bearer {access_token}"
144+
headers["Authorization"] = self.get_authorization()
138145
return super().get_response(url, auth, http_method, headers=headers, **kwargs)
139146

140147
def _build_query(self, query: str) -> Tuple[dict, str, Optional[list]]:
141-
query: dict = json_loads(query)
142-
group_id = query.pop("group_id", "")
143-
dataset_id = query.pop("dataset_id", "")
144-
query = (
145-
{
146-
"queries": [{"query": query.pop("query", "")}],
147-
"serializerSettings": {"includeNulls": True},
148-
# "impersonatedUserName": email,
149-
},
150-
)
151-
url = "/groups/{groupId}/datasets/{datasetId}/executeQueries".format_map(
152-
{
153-
"groupId": group_id,
154-
"datasetId": dataset_id,
155-
}
156-
)
157-
return url, query
148+
query_dict: dict = yaml.safe_load(query)
149+
group_id = query_dict.get("group_id")
150+
dataset_id = query_dict.get("dataset_id")
151+
json_body = {
152+
"queries": [{"query": query_dict.get("query", "")}],
153+
"serializerSettings": {"includeNulls": True},
154+
# "impersonatedUserName": email,
155+
}
156+
157+
if dataset_id is None:
158+
raise ValueError("dataset_id can't be empty")
159+
url = (
160+
"" if group_id is None else f"/groups/{group_id}"
161+
) + f"/datasets/{dataset_id}" "/executeQueries"
162+
return url, json_body
158163

159164
@classmethod
160165
def _parse_results(cls, query_results: dict):
161166
try:
162-
rows = query_results.get("results", {}).get("tables", [{}]).get("rows", [])
163-
df = pandas.from_records(data=rows)
167+
rows = (
168+
query_results.get("results", [{}])[0]
169+
.get("tables", [{}])[0]
170+
.get("rows", [])
171+
)
172+
df = pandas.DataFrame.from_records(data=rows)
164173
data = {"columns": [], "rows": []}
165174
conversions = CONVERSIONS
166175
labels = []
@@ -196,12 +205,13 @@ def _parse_results(cls, query_results: dict):
196205
return json_data, error
197206

198207
def run_query(self, query, user):
199-
url, query = self._build_query(query)
208+
url, json_body = self._build_query(query)
200209
response, error = self.get_response(
201210
http_method="post",
202211
url=url,
203-
json=query,
212+
json=json_body,
204213
)
214+
response.raise_for_status()
205215
query_results = response.json()
206216
json_data, error = self._parse_results(query_results)
207217
return json_data, error

0 commit comments

Comments
 (0)