Skip to content

Commit

Permalink
Merge pull request #262 from DocShow-AI/integrate-powerbi-reports
Browse files Browse the repository at this point in the history
Integrate powerbi reports
  • Loading branch information
rezart95 authored Feb 7, 2024
2 parents dbc1a55 + 2538b48 commit 23f5cd7
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 53 deletions.
9 changes: 9 additions & 0 deletions backend/models/powerbi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import List

from pydantic import BaseModel


class GenerateEmbededTokenRequest(BaseModel):
workspace_ids: List[str]
dataset_ids: List[str]
report_ids: List[str]
30 changes: 27 additions & 3 deletions backend/routes/powerbi_routes.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
from fastapi import APIRouter
from models.powerbi import GenerateEmbededTokenRequest
from utils.azure.azure_manager import AzureManager

powerbi_router = APIRouter()


@powerbi_router.get("/powerbi/token/")
async def get_powerbi_token():
@powerbi_router.post("/powerbi/embeded-token/")
async def generate_powerbi_token(request: GenerateEmbededTokenRequest):
azure_manager = AzureManager()
token = azure_manager.get_powerbi_token()
token = await azure_manager.get_powerbi_embeded_token(
request.workspace_ids, request.dataset_ids, request.report_ids
)
return {"token": token}


@powerbi_router.get("/powerbi/reports/")
async def get_powerbi_reports():
azure_manager = AzureManager()
reports = await azure_manager.get_powerbi_reports()
return {"reports": reports}


@powerbi_router.get("/powerbi/reports/{report_id}")
async def get_powerbi_report(report_id: str):
azure_manager = AzureManager()
report = await azure_manager.get_powerbi_report(report_id)
return {"report": report}


@powerbi_router.get("/powerbi/workspaces/")
async def get_powerbi_workspaces():
azure_manager = AzureManager()
workspaces = await azure_manager.get_powerbi_workspaces()
return {"workspaces": workspaces}
4 changes: 2 additions & 2 deletions backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

AZURE_CLIENT_ID = config("AZURE_CLIENT_ID")
AZURE_TENANT_ID = config("AZURE_TENANT_ID")
AZURE_APP_VALUE = config("AZURE_APP_VALUE")
AZURE_APP_SECRET = config("AZURE_APP_SECRET")
AZURE_SECRET_VALUE = config("AZURE_SECRET_VALUE")
AZURE_SECRET_ID = config("AZURE_SECRET_ID")

ACCESS_TOKEN_EXPIRE_MINUTES = config("ACCESS_TOKEN_EXPIRE_MINUTES", default=30)
REFRESH_TOKEN_EXPIRE_DAYS = config("REFRESH_TOKEN_EXPIRE_DAYS", default=1)
Expand Down
90 changes: 85 additions & 5 deletions backend/utils/azure/azure_manager.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,95 @@
import json
import traceback

import httpx
from azure.identity import ClientSecretCredential
from settings import AZURE_APP_SECRET, AZURE_CLIENT_ID, AZURE_TENANT_ID
from settings import AZURE_CLIENT_ID, AZURE_SECRET_VALUE, AZURE_TENANT_ID


class AzureManager:
def __init__(self):
self.credential = ClientSecretCredential(
AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_APP_SECRET
AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_SECRET_VALUE
)
self.powerbi_token = self.credential.get_token(
self.azure_token = self.credential.get_token(
"https://analysis.windows.net/powerbi/api/.default"
)

def get_powerbi_token(self):
return self.powerbi_token.token
def get_azure_token(self):
return self.azure_token.token

async def get_powerbi_embeded_token(self, workspace_ids, dataset_ids, report_ids):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.get_azure_token()}",
}
async with httpx.AsyncClient() as client:
try:
payload = {
"targetWorkspaces": [
{"id": workspace_id} for workspace_id in workspace_ids
],
"datasets": [
{"id": dataset_id, "xmlaPermissions": "ReadOnly"}
for dataset_id in dataset_ids
],
"reports": [
{"id": report_id, "allowEdit": True} for report_id in report_ids
],
}
response = await client.post(
"https://api.powerbi.com/v1.0/myorg/GenerateToken",
headers=headers,
content=json.dumps(payload),
)
print(f"Response status code: {response.status_code}")
token = response.json().get("token")
return token
except Exception as e:
print(f"An error occurred: {e}")
traceback.print_exc()
return None

async def get_powerbi_reports(self):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.get_azure_token()}",
}
print(headers)
async with httpx.AsyncClient() as client:
try:
response = await client.get(
# "https://analysis.windows.net/powerbi/api/v1.0/myorg/reports",
# headers=headers
"https://api.powerbi.com/v1.0/myorg/groups/361eea67-d03b-4799-8779-7c1d6c175182/reports",
headers=headers,
)
print(f"Response status code: {response.status_code}")
if response.status_code == 200:
response_json = response.json()
print(response_json)
return response_json
else:
print(f"Response content: {response.content}")
return None
except Exception as e:
print(f"An error occurred: {e}")
traceback.print_exc()
return None

async def get_powerbi_report(self, report_id):
headers = {"Authorization": f"Bearer {self.get_azure_token()}"}
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.powerbi.com/v1.0/myorg/groups/361eea67-d03b-4799-8779-7c1d6c175182/reports/{report_id}",
headers=headers,
)
return response.json()

async def get_powerbi_workspaces(self):
headers = {"Authorization": f"Bearer {self.get_azure_token()}"}
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.powerbi.com/v1.0/myorg/groups", headers=headers
)
return response.json()
64 changes: 64 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
"copy-to-clipboard": "^3.3.3",
"date-fns": "^2.30.0",
"js-cookie": "^3.0.5",
"powerbi-client": "^2.22.3",
"primereact": "^10.4.0",
"qs": "^6.11.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-powerbi": "^0.9.1",
"react-router-dom": "^6.19.0",
"validator": "^13.11.0"
},
Expand Down
25 changes: 18 additions & 7 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import React, { useEffect } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
import "./api/axiosInterceptor";
import { AuthProvider, useAuth } from "./contexts/AuthContext";
import { APP_ENV } from "./utils/constants";
import AppLayout from "./components/layouts/AppLayout";
import LandingLayout from "./components/layouts/LandingLayout";
import RequireAuth from "./components/auth/RequireAuth";
import RequireSysAdminAuth from "./components/auth/RequireSysAdminAuth";
import AppLayout from "./components/layouts/AppLayout";
import LandingLayout from "./components/layouts/LandingLayout";
import { AuthProvider, useAuth } from "./contexts/AuthContext";
import AboutPage from "./pages/about/AboutPage";
import AdminPage from "./pages/admin/AdminPage";
import AIAnalystPage from "./pages/ai-analyst/AIAnalystPage";
import BlogPage from "./pages/blog/BlogPage";
import ChangePasswordPage from "./pages/change-password/ChangePasswordPage";
import CreateChartPage from "./pages/charts/CreateChartPage";
import CreateDashboardPage from "./pages/dashboards/CreateDashboard";
import DashboardMenuPage from "./pages/dashboards/DashboardsMenuPage";
import Dashboard from "./pages/dashboards/Dashboard";
import DashboardMenuPage from "./pages/dashboards/DashboardsMenuPage";
import ForgotPasswordPage from "./pages/forgot-password/ForgotPasswordPage";
import LandingPage from "./pages/landing/LandingPage";
import LoginPage from "./pages/login/LoginPage";
import Logout from "./pages/logout/LogoutPage";
import PricingPage from "./pages/pricing/PricingPage";
import RegisterPage from "./pages/register/RegisterPage";
import ResetPasswordPage from "./pages/reset-password/ResetPasswordPage";
import UploadPage from "./pages/upload/UploadPage";
import UserPage from "./pages/user/UserPage";
import VerifyEmailPage from "./pages/verify-email/VerifyEmailPage";
import Logout from "./pages/logout/LogoutPage";
import ReportPage from "./pages/dashboards/ReportPage";
import { APP_ENV } from "./utils/constants";

function AppWrapper() {
return (
Expand Down Expand Up @@ -146,6 +147,16 @@ function App() {
</RequireAuth>
}
/>
<Route
path="/dashboards/:report_id"
element={
<RequireAuth>
<AppLayout>
<ReportPage />
</AppLayout>
</RequireAuth>
}
/>
<Route
path="/dashboards/create"
element={
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/pages/dashboards/Dashboard.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { Box, Grid, IconButton, Paper, Typography } from "@mui/material";
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
import { useNavigate } from "react-router-dom";
import ChartDisplay from "../charts/ChartDisplay";
import { Box, Grid, IconButton, Paper, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { API_URL } from "../../utils/constants";
import ChartDisplay from "../charts/ChartDisplay";

function Dashboard() {
const { dashboardId } = useParams();
Expand Down
Loading

0 comments on commit 23f5cd7

Please sign in to comment.