Merged PR 149937: Rename package to powerbiclient

## In this PR:
- Renamed widget
- Renamed traitlets
- Renamed frontend vars
- Updated test cases

**Note: Do not merge**

[Pipeline results](https://dev.azure.com/powerbi/Embedded/_build/results?buildId=2734029&view=results)

Related work items: #537728
This commit is contained in:
Mayur Garhwal 2021-03-04 06:24:01 +00:00
Родитель 29763851f9
Коммит b85f8949a9
36 изменённых файлов: 887 добавлений и 893 удалений

Просмотреть файл

@ -1,2 +1,2 @@
[run]
omit = powerbi_client/tests/*
omit = powerbiclient/tests/*

6
.gitignore поставляемый
Просмотреть файл

@ -145,15 +145,15 @@ $RECYCLE.BIN/
# ----
**/node_modules/
powerbi_client/nbextension/static/index.*
powerbi_client/labextension/*.tgz
powerbiclient/nbextension/static/index.*
powerbiclient/labextension/*.tgz
# Coverage data
# -------------
**/coverage/
# Packed lab extensions
powerbi_client/labextension
powerbiclient/labextension
# VS Code
.vscode

Просмотреть файл

@ -25,7 +25,7 @@ static_analysis_options:
- from: 'src\'
include:
- '**/*.*'
- from: 'powerbi_client\'
- from: 'powerbiclient\'
include:
- '**/*.*'
@ -35,7 +35,7 @@ static_analysis_options:
- 'demo\**\*' # Exclude path 'Localize'.
- 'test\**\*'
- 'node_modules\**\*'
- 'powerbi_client\tests\**\*'
- 'powerbiclient\tests\**\*'
binskim_options:
files_to_scan:
@ -43,7 +43,7 @@ static_analysis_options:
- 'examples\**\*' # Exclude path 'Localize'.
- 'tests\**\*'
- 'node_modules\**\*'
- 'powerbi_client\tests\**\*'
- 'powerbiclient\tests\**\*'
package_sources:
npm:
@ -87,7 +87,7 @@ build:
- '**/examples/**/*.*'
- '**/node_modules/**/*.*'
- '**/tests/**/*.*'
- '**/powerbi_client/tests/**/*.*'
- '**/powerbiclient/tests/**/*.*'
# All build stage artifacts get signed right after the build stage
# because the global signing profile is defined.

Просмотреть файл

@ -3,9 +3,9 @@ $exitCode = 0;
# TODO: Run front-end test script when available
# Run python kernel test scripts
Write-Host "start: pytest powerbi_client"
& python -m pytest powerbi_client --cov -v
Write-Host "done: pytest powerbi_client"
Write-Host "start: pytest powerbiclient"
& python -m pytest powerbiclient --cov -v
Write-Host "done: pytest powerbiclient"
$exitCode += $LASTEXITCODE;
exit $exitCode;

Просмотреть файл

@ -56,7 +56,7 @@ script:
if [[ $GROUP == python ]]; then
EXIT_STATUS=0
pushd $(mktemp -d)
py.test -l --cov-report xml:$TRAVIS_BUILD_DIR/coverage.xml --cov=powerbi_client --pyargs powerbi_client || EXIT_STATUS=$?
py.test -l --cov-report xml:$TRAVIS_BUILD_DIR/coverage.xml --cov=powerbiclient --pyargs powerbiclient || EXIT_STATUS=$?
popd
(exit $EXIT_STATUS)
elif [[ $GROUP == js ]]; then

Просмотреть файл

@ -8,7 +8,7 @@ include .coverage.rc
include tsconfig.json
include package.json
include webpack.config.js
include powerbi_client/labextension/*.tgz
include powerbiclient/labextension/*.tgz
# Documentation
graft docs
@ -25,7 +25,7 @@ graft tests
prune tests/build
# Javascript files
graft powerbi_client/nbextension
graft powerbiclient/nbextension
graft src
graft css
prune **/node_modules

Просмотреть файл

@ -8,20 +8,20 @@ A Custom Jupyter Widget Library
You can install using `pip`:
```bash
pip install powerbi_client
pip install powerbiclient
```
Or if you use jupyterlab:
```bash
pip install powerbi_client
pip install powerbiclient
jupyter labextension install @jupyter-widgets/jupyterlab-manager
```
If you are using Jupyter Notebook 5.2 or earlier, you may also need to enable
the nbextension:
```bash
jupyter nbextension enable --py [--sys-prefix|--user|--system] powerbi_client
jupyter nbextension enable --py [--sys-prefix|--user|--system] powerbiclient
```
## Demo
@ -58,8 +58,8 @@ jupyter labextension install .
For classic notebook, you can run:
```
jupyter nbextension install --sys-prefix --symlink --overwrite --py powerbi_client
jupyter nbextension enable --sys-prefix --py powerbi_client
jupyter nbextension install --sys-prefix --symlink --overwrite --py powerbiclient
jupyter nbextension enable --sys-prefix --py powerbiclient
```
__Note__ that the `--symlink` flag doesn't work on Windows, so you will here have to run
@ -89,7 +89,7 @@ For classic jupyter notebook:
1. Copy output to jupyter directory
```bash
jupyter nbextension install --sys-prefix --overwrite --py powerbi_client
jupyter nbextension install --sys-prefix --overwrite --py powerbiclient
```
1. Reload webpage in browser
@ -111,4 +111,4 @@ If you make changes to the python code then you will need to restart the noteboo
`npm run debug`<br/>
#### Python:
1. `pytest powerbi_client --cov -v`
1. `pytest powerbiclient --cov -v`

Просмотреть файл

@ -48,14 +48,14 @@ build: off
before_test:
- git config --global user.email appveyor@fake.com
- git config --global user.name "AppVeyor CI"
- set "tmptestdir=%tmp%\powerbi_client-%RANDOM%"
- set "tmptestdir=%tmp%\powerbiclient-%RANDOM%"
- mkdir "%tmptestdir%"
- cd "%tmptestdir%"
# to run your custom scripts instead of automatic tests
test_script:
- 'py.test -l --cov-report xml:"%APPVEYOR_BUILD_FOLDER%\coverage.xml" --cov=powerbi_client --pyargs powerbi_client'
- 'py.test -l --cov-report xml:"%APPVEYOR_BUILD_FOLDER%\coverage.xml" --cov=powerbiclient --pyargs powerbiclient'
on_success:
- cd "%APPVEYOR_BUILD_FOLDER%"

Просмотреть файл

@ -9,4 +9,4 @@ coverage:
default:
target: 0%
ignore:
- "powerbi_client/tests"
- "powerbiclient/tests"

Просмотреть файл

@ -4,7 +4,7 @@
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = powerbi_client
SPHINXPROJ = powerbiclient
SOURCEDIR = source
BUILDDIR = build

Просмотреть файл

@ -1,5 +1,5 @@
name: powerbi_client_docs
name: powerbiclient_docs
channels:
- conda-forge
dependencies:

Просмотреть файл

@ -9,7 +9,7 @@ if "%SPHINXBUILD%" == "" (
)
set SOURCEDIR=source
set BUILDDIR=build
set SPHINXPROJ=powerbi_client
set SPHINXPROJ=powerbiclient
if "%1" == "" goto help

Просмотреть файл

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# powerbi_client documentation build configuration file
# powerbiclient documentation build configuration file
#
# This file is execfile()d with the current directory set to its
# containing dir.
@ -54,7 +54,7 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = 'powerbi_client'
project = 'powerbiclient'
copyright = '2020, Microsoft'
author = 'Microsoft'
@ -69,7 +69,7 @@ author = 'Microsoft'
import os
here = os.path.dirname(__file__)
repo = os.path.join(here, '..', '..')
_version_py = os.path.join(repo, 'powerbi_client', '_version.py')
_version_py = os.path.join(repo, 'powerbiclient', '_version.py')
version_ns = {}
with open(_version_py) as f:
exec(f.read(), version_ns)
@ -116,7 +116,7 @@ html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'powerbi_clientdoc'
htmlhelp_basename = 'powerbiclientdoc'
# -- Options for LaTeX output ---------------------------------------------
@ -143,7 +143,7 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'powerbi_client.tex', 'powerbi_client Documentation',
(master_doc, 'powerbiclient.tex', 'powerbiclient Documentation',
'Microsoft', 'manual'),
]
@ -154,8 +154,8 @@ latex_documents = [
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc,
'powerbi_client',
'powerbi_client Documentation',
'powerbiclient',
'powerbiclient Documentation',
[author], 1)
]
@ -167,10 +167,10 @@ man_pages = [
# dir menu entry, description, category)
texinfo_documents = [
(master_doc,
'powerbi_client',
'powerbi_client Documentation',
'powerbiclient',
'powerbiclient Documentation',
author,
'powerbi_client',
'powerbiclient',
'A Custom Jupyter Widget Library',
'Miscellaneous'),
]

Просмотреть файл

@ -3,7 +3,7 @@ Developer install
=================
To install a developer version of powerbi_client, you will first need to clone
To install a developer version of powerbiclient, you will first need to clone
the repository::
git clone https://github.com/Microsoft/powerbi-jupyter
@ -17,9 +17,9 @@ Next, install it with a develop install using pip::
If you are planning on working on the JS/frontend code, you should also do
a link installation of the extension::
jupyter nbextension install [--sys-prefix / --user / --system] --symlink --py powerbi_client
jupyter nbextension install [--sys-prefix / --user / --system] --symlink --py powerbiclient
jupyter nbextension enable [--sys-prefix / --user / --system] --py powerbi_client
jupyter nbextension enable [--sys-prefix / --user / --system] --py powerbiclient
with the `appropriate flag`_. Or, if you are using Jupyterlab::

Просмотреть файл

@ -1,5 +1,5 @@
powerbi_client
powerbiclient
=====================================
Version: |release|
@ -10,13 +10,13 @@ A Custom Jupyter Widget Library
Quickstart
----------
To get started with powerbi_client, install with pip::
To get started with powerbiclient, install with pip::
pip install powerbi_client
pip install powerbiclient
or with conda::
conda install powerbi_client
conda install powerbiclient
Contents

Просмотреть файл

@ -5,22 +5,22 @@ Installation
============
The simplest way to install powerbi_client is via pip::
The simplest way to install powerbiclient is via pip::
pip install powerbi_client
pip install powerbiclient
or via conda::
conda install powerbi_client
conda install powerbiclient
If you installed via pip, and notebook version < 5.3, you will also have to
install / configure the front-end extension as well. If you are using classic
notebook (as opposed to Jupyterlab), run::
jupyter nbextension install [--sys-prefix / --user / --system] --py powerbi_client
jupyter nbextension install [--sys-prefix / --user / --system] --py powerbiclient
jupyter nbextension enable [--sys-prefix / --user / --system] --py powerbi_client
jupyter nbextension enable [--sys-prefix / --user / --system] --py powerbiclient
with the `appropriate flag`_. If you are using Jupyterlab, install the extension
with::

Просмотреть файл

@ -13,7 +13,7 @@
"metadata": {},
"outputs": [],
"source": [
"from powerbi_client import Report\n",
"from powerbiclient import Report\n",
"import requests\n",
"from io import StringIO\n",
"import pandas as pd\n",
@ -33,7 +33,7 @@
" \n",
" Code snippet:\n",
" ``` py\n",
" from powerbi_client.authentication import DeviceCodeLoginAuthentication\n",
" from powerbiclient.authentication import DeviceCodeLoginAuthentication\n",
" device_auth = DeviceCodeLoginAuthentication()\n",
" token_with_device_code = device_auth.get_access_token()\n",
" ```\n",
@ -41,7 +41,7 @@
" \n",
" Code snippet:\n",
" ``` py\n",
" from powerbi_client.authentication import InteractiveLoginAuthentication\n",
" from powerbiclient.authentication import InteractiveLoginAuthentication\n",
" interactive_auth = InteractiveLoginAuthentication()\n",
" token_with_redirect = interactive_auth.get_access_token()\n",
" \n",
@ -455,7 +455,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.0"
"version": "3.8.6"
}
},
"nbformat": 4,

Просмотреть файл

@ -29,14 +29,14 @@
},
"scripts": {
"build": "npm run build:lib && npm run build:nbextension",
"build:labextension": "npm run clean:labextension && mkdirp powerbi_client/labextension && cd powerbi_client/labextension && npm pack ../..",
"build:labextension": "npm run clean:labextension && mkdirp powerbiclient/labextension && cd powerbiclient/labextension && npm pack ../..",
"build:lib": "tsc",
"build:nbextension": "webpack -p",
"build:all": "npm run build:labextension && npm run build:nbextension",
"clean": "npm run clean:lib && npm run clean:nbextension",
"clean:lib": "rimraf lib",
"clean:labextension": "rimraf powerbi_client/labextension",
"clean:nbextension": "rimraf powerbi_client/nbextension/static/index.js",
"clean:labextension": "rimraf powerbiclient/labextension",
"clean:nbextension": "rimraf powerbiclient/nbextension/static/index.js",
"lint": "eslint . --ext .ts,.tsx --fix",
"lint:check": "eslint . --ext .ts,.tsx",
"prepack": "npm run build:lib",

Просмотреть файл

@ -1,5 +0,0 @@
{
"load_extensions": {
"powerbi_client/extension": true
}
}

5
powerbiclient.json Normal file
Просмотреть файл

@ -0,0 +1,5 @@
{
"load_extensions": {
"powerbiclient/extension": true
}
}

Просмотреть файл

@ -1,10 +1,10 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
from .report import Report
from ._version import __version__, version_info
from .nbextension import _jupyter_nbextension_paths
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
from .report import Report
from ._version import __version__, version_info
from .nbextension import _jupyter_nbextension_paths

Просмотреть файл

@ -1,12 +1,12 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
"""
Information about the frontend package of the widgets.
"""
module_name = "powerbi-client-frontend"
module_version = "^0.1.0"
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
"""
Information about the frontend package of the widgets.
"""
module_name = "powerbi-client-frontend"
module_version = "^0.1.0"

Просмотреть файл

@ -1,7 +1,7 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
version_info = (0, 1, 0, 'dev')
__version__ = ".".join(map(str, version_info))
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
version_info = (0, 1, 0, 'dev')
__version__ = ".".join(map(str, version_info))

Просмотреть файл

Просмотреть файл

@ -1,13 +1,13 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft
# Distributed under the terms of the Modified BSD License.
def _jupyter_nbextension_paths():
return [{
'section': 'notebook',
'src': 'nbextension/static',
'dest': 'powerbi_client',
'require': 'powerbi_client/extension'
}]
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft
# Distributed under the terms of the Modified BSD License.
def _jupyter_nbextension_paths():
return [{
'section': 'notebook',
'src': 'nbextension/static',
'dest': 'powerbiclient',
'require': 'powerbiclient/extension'
}]

Просмотреть файл

@ -1,17 +1,17 @@
// Entry point for the notebook bundle containing custom model definitions.
//
define(function() {
"use strict";
window['requirejs'].config({
map: {
'*': {
'powerbi-client-frontend': 'nbextensions/powerbi_client/index',
},
}
});
// Export the required load_ipython_extension function
return {
load_ipython_extension : function() {}
};
});
// Entry point for the notebook bundle containing custom model definitions.
//
define(function() {
"use strict";
window['requirejs'].config({
map: {
'*': {
'powerbi-client-frontend': 'nbextensions/powerbiclient/index',
},
}
});
// Export the required load_ipython_extension function
return {
load_ipython_extension : function() {}
};
});

Просмотреть файл

@ -1,412 +1,410 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
"""
TODO: Add module docstring
"""
import time
import requests
from IPython import get_ipython
from ipywidgets import DOMWidget
from jupyter_ui_poll import ui_events
from traitlets import Bool, Dict, Float, Unicode, List, observe
from ._frontend import module_name, module_version
from .authentication import DeviceCodeLoginAuthentication
class Report(DOMWidget):
"""PowerBI report embedding widget"""
# Name of the widget view class in front-end
_view_name = Unicode('ReportView').tag(sync=True)
# Name of the widget model class in front-end
_model_name = Unicode('ReportModel').tag(sync=True)
# Name of the front-end module containing widget view
_view_module = Unicode('powerbi-client-frontend').tag(sync=True)
# Name of the front-end module containing widget model
_model_module = Unicode('powerbi-client-frontend').tag(sync=True)
# Version of the front-end module containing widget view
_view_module_version = Unicode('^0.1.0').tag(sync=True)
# Version of the front-end module containing widget model
_model_module_version = Unicode('^0.1.0').tag(sync=True)
# Default values for widget traits
VISUAL_DATA_DEFAULT_STATE = ''
EXPORT_VISUAL_DATA_REQUEST_DEFAULT_STATE = {}
REGISTERED_EVENT_HANDLERS_DEFAULT_STATE = {}
EVENT_DATA_DEFAULT_STATE = {
'event_name': None,
'event_details': None
}
REPORT_FILTER_REQUEST_DEFAULT_STATE = {
'filters': [],
'request_completed': True
}
GET_PAGES_REQUEST_DEFAULT_STATE = False
REPORT_PAGES_DEFAULT_STATE = []
GET_VISUALS_DEFAULT_PAGE_NAME = ''
PAGE_VISUALS_DEFAULT_STATE = []
GET_BOOKMARKS_REQUEST_DEFAULT_STATE = False
REPORT_BOOKMARKS_DEFAULT_STATE = []
REPORT_BOOKMARK_DEFAULT_NAME = ''
TOKEN_EXPIRED_DEFAULT_STATE = False
# Other constants
REPORT_NOT_EMBEDDED_MESSAGE = "Power BI report is not embedded"
# Process upto n UI events per iteration
PROCESS_EVENTS_ITERATION = 3
# Check for UI every n seconds
POLLING_INTERVAL = 0.5
# Authentication object
_auth = None
# Widget specific properties.
# Widget properties are defined as traitlets. Any property tagged with `sync=True`
# is automatically synced to the frontend *any* time it changes in Python.
# It is synced back to Python from the frontend *any* time the model is touched in frontend.
# TODO: Add trait validation
_embed_config = Dict(None).tag(sync=True)
_embedded = Bool(False).tag(sync=True)
container_height = Float(0).tag(sync=True)
container_width = Float(0).tag(sync=True)
# TODO: Add trait validation
# TODO: Start with _
export_visual_data_request = Dict(None).tag(sync=True)
# TODO: Start with _
visual_data = Unicode(VISUAL_DATA_DEFAULT_STATE).tag(sync=True)
_event_data = Dict(EVENT_DATA_DEFAULT_STATE).tag(sync=True)
# TODO: Add trait validation
_report_filters_request = Dict(REPORT_FILTER_REQUEST_DEFAULT_STATE).tag(sync=True)
_get_pages_request = Bool(GET_PAGES_REQUEST_DEFAULT_STATE).tag(sync=True)
_report_pages = List(REPORT_PAGES_DEFAULT_STATE).tag(sync=True)
_get_visuals_page_name = Unicode(GET_VISUALS_DEFAULT_PAGE_NAME).tag(sync=True)
_page_visuals = List(PAGE_VISUALS_DEFAULT_STATE).tag(sync=True)
_report_bookmark_name = Unicode(REPORT_BOOKMARK_DEFAULT_NAME).tag(sync=True)
_report_bookmarks = List(REPORT_BOOKMARKS_DEFAULT_STATE).tag(sync=True)
_get_bookmarks_request = Bool(GET_BOOKMARKS_REQUEST_DEFAULT_STATE).tag(sync=True)
_token_expired = Bool(TOKEN_EXPIRED_DEFAULT_STATE).tag(sync=True)
# Methods
def __init__(self, access_token=None, token_type=0, embed_url=None, group_id=None, report_id=None, auth=None, **kwargs):
token_expiration = 0
try:
# Get access token for the report using authentication
if not access_token:
if not auth:
if not Report._auth:
# Set DeviceCodeLoginAuthentication to be the default global authentication method
Report._auth = DeviceCodeLoginAuthentication()
auth = Report._auth
self._auth = auth
access_token = self._auth.get_access_token()
token_type = 0
token_expiration = self._auth.get_access_token_details().get('id_token_claims').get('exp')
# Get embed URL for the report using access token
if not embed_url:
if not group_id or not report_id:
raise Exception("Group Id and Report Id are required")
if token_type == 1:
raise Exception("Cannot get embed URL using embed token")
request_url = "https://api.powerbi.com/v1.0/myorg/groups/" + group_id + "/reports/" + report_id
response = requests.get(request_url, headers={'Authorization': 'Bearer ' + access_token})
embed_url = response.json()['embedUrl']
except:
raise Exception("Could not create Access token or Embed URL")
# Tells if Power BI events are being observed
self._observing_events = False
# Registered Power BI event handlers methods
self._registered_event_handlers = dict(
self.REGISTERED_EVENT_HANDLERS_DEFAULT_STATE)
self._set_embed_config(access_token, embed_url, token_type, token_expiration)
self.observe(self._update_access_token, '_token_expired')
# Init parent class DOMWidget
super(Report, self).__init__(**kwargs)
def _update_access_token(self, change):
if change.new == True:
if not self._auth:
raise Exception("Authentication context not found")
self._auth.refresh_token()
self._set_embed_config(self._auth.get_access_token(), self._embed_config['embedUrl'], self._embed_config['tokenType'], self._auth.get_access_token_details().get('id_token_claims').get('exp'))
self._token_expired = bool(self.TOKEN_EXPIRED_DEFAULT_STATE)
def set_access_token(self, access_token):
"""Set access token for Power BI report
Args:
access_token (string): report access token
"""
self._set_embed_config(access_token=access_token, embed_url=self._embed_config['embedUrl'], token_type=self._embed_config['tokenType'])
def _set_embed_config(self, access_token, embed_url, token_type=0, token_expiration=0):
"""
TODO: Add docstring
"""
self._embed_config = {
'type': 'report',
'accessToken': access_token,
'embedUrl': embed_url,
'tokenType': token_type,
'tokenExpiration': token_expiration
}
self._embedded = False
def set_dimensions(self, container_height, container_width):
"""Set width and height of Power BI report in px
Args:
container_height (number): report height
container_width (number): report width
"""
self.container_height = container_height
self.container_width = container_width
def export_visual_data(self, page_name, visual_name, rows=10, underlying_data=False):
"""Returns the data of given visual of the embedded Power BI report
Args:
page_name (string): Page name of the embedded report
visual_name (string): Visual's unique name
rows (int, optional): Number of rows of data. Defaults to 10
underlying_data (boolean, optional): Choice to show the underlying data or not. Default is False.
Returns:
string: visual's exported data
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
# Start exporting data on client side
self.export_visual_data_request = {
'pageName': page_name,
'visualName': visual_name,
'rows': rows,
'underlyingData': underlying_data
}
# Check if ipython kernel is available
if get_ipython():
# Wait for client-side to send visual data
with ui_events() as ui_poll:
# While visual data is not received
while self.visual_data == self.VISUAL_DATA_DEFAULT_STATE:
ui_poll(self.PROCESS_EVENTS_ITERATION)
time.sleep(self.POLLING_INTERVAL)
exported_data = self.visual_data
# Reset the export_visual_data_request and visual_data's value
self.export_visual_data_request = dict(self.EXPORT_VISUAL_DATA_REQUEST_DEFAULT_STATE)
self.visual_data = self.VISUAL_DATA_DEFAULT_STATE
return exported_data
def on(self, event, callback):
"""Register a callback to execute when the report emits the target event
Parameters
Args:
event (string): Name of Power BI event (eg. 'loaded', 'rendered', 'error')
callback (function): User defined function. Callback function is invoked with event details as parameter
"""
# TODO: Value of event should be from one of the Report.allowedEvents array
self._registered_event_handlers[event] = callback
def get_event_data(change):
event_data = change.new
event_name = event_data['event_name']
event_details = event_data['event_details']
# Do not invoke callback when _event_data trait is reset
if event_name is None:
return
# Check if a handler is registered for the current event
if event_name not in self._registered_event_handlers:
return
event_handler = self._registered_event_handlers[event_name]
event_handler(event_details)
# Reset the _event_data trait, so as to receive next event
self._event_data = dict(self.EVENT_DATA_DEFAULT_STATE)
# Check if already observing events
if not self._observing_events:
# Prevents calling DOMWidget.observe() again
self._observing_events = True
# Start observing Power BI events
self.observe(get_event_data, '_event_data')
def update_filters(self, filters):
"""Set report level filters in the embedded report.
Currently supports models.FiltersOperations.Add
Args:
filters ([models.ReportLevelFilters]): List of report level filters
Raises:
Exception: When report is not embedded
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
self._report_filters_request = {
'filters': filters,
'request_completed': False
}
# TODO: Should we wait for the filters to be applied
def remove_filters(self):
"""Remove all report level filters from the embedded report
Raises:
Exception: When report is not embedded
"""
self.update_filters([])
def get_pages(self):
"""Returns the list of pages of the embedded Power BI report
Returns:
string: list of pages
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
# Start getting pages on client side
self._get_pages_request = True
# Check if ipython kernel is available
if get_ipython():
# Wait for client-side to send list of pages
with ui_events() as ui_poll:
# While list of report pages is not received
while self._report_pages == self.REPORT_PAGES_DEFAULT_STATE:
ui_poll(self.PROCESS_EVENTS_ITERATION)
time.sleep(self.POLLING_INTERVAL)
pages = self._report_pages
# Reset the get_pages_request and report_pages's value
self._get_pages_request = bool(self.GET_PAGES_REQUEST_DEFAULT_STATE)
self._report_pages = list(self.REPORT_PAGES_DEFAULT_STATE)
return pages
def visuals_on_page(self, page_name):
"""Returns the list of visuals of the given page of the embedded Power BI report
Args:
page_name (string): Page name of the embedded report
Returns:
string: list of visuals
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
# Start getting visuals on client side
self._get_visuals_page_name = page_name
# Check if ipython kernel is available
if get_ipython():
# Wait for client-side to send list of visuals
with ui_events() as ui_poll:
# While list of visuals is not received
while self._page_visuals == self.PAGE_VISUALS_DEFAULT_STATE:
ui_poll(self.PROCESS_EVENTS_ITERATION)
time.sleep(self.POLLING_INTERVAL)
visuals = self._page_visuals
# Reset the get_visuals_page_name and page_visuals's value
self._get_visuals_page_name = self.GET_VISUALS_DEFAULT_PAGE_NAME
self._page_visuals = list(self.PAGE_VISUALS_DEFAULT_STATE)
return visuals
def set_bookmark(self, bookmark_name):
"""Applies a bookmark by name on the embedded report.
Args:
bookmark_name (string) : Bookmark Name
Raises:
Exception: When report is not embedded
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
self._report_bookmark_name = bookmark_name
def get_bookmarks(self):
"""Returns the list of bookmarks of the embedded Power BI report
Returns:
list: list of bookmarks
Raises:
Exception: When report is not embedded
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
# Start getting bookmarks on client side
self._get_bookmarks_request = True
# Check if ipython kernel is available
if get_ipython():
# Wait for client-side to send list of bookmarks
with ui_events() as ui_poll:
# While list of report bookmark(s) is not received
while self._report_bookmarks == self.REPORT_BOOKMARKS_DEFAULT_STATE:
ui_poll(self.PROCESS_EVENTS_ITERATION)
time.sleep(self.POLLING_INTERVAL)
bookmarks = self._report_bookmarks
if bookmarks == ['']:
bookmarks = self.REPORT_BOOKMARKS_DEFAULT_STATE
# Reset the _get_bookmarks_request and _report_bookmarks values
self._get_bookmarks_request = bool(self.GET_BOOKMARKS_REQUEST_DEFAULT_STATE)
self._report_bookmarks = list(self.REPORT_BOOKMARKS_DEFAULT_STATE)
return bookmarks
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
"""
TODO: Add module docstring
"""
import time
import requests
from IPython import get_ipython
from ipywidgets import DOMWidget
from jupyter_ui_poll import ui_events
from traitlets import Bool, Dict, Float, Unicode, List, observe
from ._frontend import module_name, module_version
from .authentication import DeviceCodeLoginAuthentication
class Report(DOMWidget):
"""PowerBI report embedding widget"""
# Name of the widget view class in front-end
_view_name = Unicode('ReportView').tag(sync=True)
# Name of the widget model class in front-end
_model_name = Unicode('ReportModel').tag(sync=True)
# Name of the front-end module containing widget view
_view_module = Unicode('powerbi-client-frontend').tag(sync=True)
# Name of the front-end module containing widget model
_model_module = Unicode('powerbi-client-frontend').tag(sync=True)
# Version of the front-end module containing widget view
_view_module_version = Unicode('^0.1.0').tag(sync=True)
# Version of the front-end module containing widget model
_model_module_version = Unicode('^0.1.0').tag(sync=True)
# Default values for widget traits
VISUAL_DATA_DEFAULT_STATE = ''
EXPORT_VISUAL_DATA_REQUEST_DEFAULT_STATE = {}
REGISTERED_EVENT_HANDLERS_DEFAULT_STATE = {}
EVENT_DATA_DEFAULT_STATE = {
'event_name': None,
'event_details': None
}
REPORT_FILTER_REQUEST_DEFAULT_STATE = {
'filters': [],
'request_completed': True
}
GET_PAGES_REQUEST_DEFAULT_STATE = False
REPORT_PAGES_DEFAULT_STATE = []
GET_VISUALS_DEFAULT_PAGE_NAME = ''
PAGE_VISUALS_DEFAULT_STATE = []
GET_BOOKMARKS_REQUEST_DEFAULT_STATE = False
REPORT_BOOKMARKS_DEFAULT_STATE = []
REPORT_BOOKMARK_DEFAULT_NAME = ''
TOKEN_EXPIRED_DEFAULT_STATE = False
# Other constants
REPORT_NOT_EMBEDDED_MESSAGE = "Power BI report is not embedded"
# Process upto n UI events per iteration
PROCESS_EVENTS_ITERATION = 3
# Check for UI every n seconds
POLLING_INTERVAL = 0.5
# Authentication object
_auth = None
# Widget specific properties.
# Widget properties are defined as traitlets. Any property tagged with `sync=True`
# is automatically synced to the frontend *any* time it changes in Python.
# It is synced back to Python from the frontend *any* time the model is touched in frontend.
# TODO: Add trait validation
_embed_config = Dict(None).tag(sync=True)
_embedded = Bool(False).tag(sync=True)
container_height = Float(0).tag(sync=True)
container_width = Float(0).tag(sync=True)
# TODO: Add trait validation
_export_visual_data_request = Dict(None).tag(sync=True)
_visual_data = Unicode(VISUAL_DATA_DEFAULT_STATE).tag(sync=True)
_event_data = Dict(EVENT_DATA_DEFAULT_STATE).tag(sync=True)
# TODO: Add trait validation
_report_filters_request = Dict(REPORT_FILTER_REQUEST_DEFAULT_STATE).tag(sync=True)
_get_pages_request = Bool(GET_PAGES_REQUEST_DEFAULT_STATE).tag(sync=True)
_report_pages = List(REPORT_PAGES_DEFAULT_STATE).tag(sync=True)
_get_visuals_page_name = Unicode(GET_VISUALS_DEFAULT_PAGE_NAME).tag(sync=True)
_page_visuals = List(PAGE_VISUALS_DEFAULT_STATE).tag(sync=True)
_report_bookmark_name = Unicode(REPORT_BOOKMARK_DEFAULT_NAME).tag(sync=True)
_report_bookmarks = List(REPORT_BOOKMARKS_DEFAULT_STATE).tag(sync=True)
_get_bookmarks_request = Bool(GET_BOOKMARKS_REQUEST_DEFAULT_STATE).tag(sync=True)
_token_expired = Bool(TOKEN_EXPIRED_DEFAULT_STATE).tag(sync=True)
# Methods
def __init__(self, access_token=None, token_type=0, embed_url=None, group_id=None, report_id=None, auth=None, **kwargs):
token_expiration = 0
try:
# Get access token for the report using authentication
if not access_token:
if not auth:
if not Report._auth:
# Set DeviceCodeLoginAuthentication to be the default global authentication method
Report._auth = DeviceCodeLoginAuthentication()
auth = Report._auth
self._auth = auth
access_token = self._auth.get_access_token()
token_type = 0
token_expiration = self._auth.get_access_token_details().get('id_token_claims').get('exp')
# Get embed URL for the report using access token
if not embed_url:
if not group_id or not report_id:
raise Exception("Group Id and Report Id are required")
if token_type == 1:
raise Exception("Cannot get embed URL using embed token")
request_url = "https://api.powerbi.com/v1.0/myorg/groups/" + group_id + "/reports/" + report_id
response = requests.get(request_url, headers={'Authorization': 'Bearer ' + access_token})
embed_url = response.json()['embedUrl']
except:
raise Exception("Could not create Access token or Embed URL")
# Tells if Power BI events are being observed
self._observing_events = False
# Registered Power BI event handlers methods
self._registered_event_handlers = dict(
self.REGISTERED_EVENT_HANDLERS_DEFAULT_STATE)
self._set_embed_config(access_token, embed_url, token_type, token_expiration)
self.observe(self._update_access_token, '_token_expired')
# Init parent class DOMWidget
super(Report, self).__init__(**kwargs)
def _update_access_token(self, change):
if change.new == True:
if not self._auth:
raise Exception("Authentication context not found")
self._auth.refresh_token()
self._set_embed_config(self._auth.get_access_token(), self._embed_config['embedUrl'], self._embed_config['tokenType'], self._auth.get_access_token_details().get('id_token_claims').get('exp'))
self._token_expired = bool(self.TOKEN_EXPIRED_DEFAULT_STATE)
def set_access_token(self, access_token):
"""Set access token for Power BI report
Args:
access_token (string): report access token
"""
self._set_embed_config(access_token=access_token, embed_url=self._embed_config['embedUrl'], token_type=self._embed_config['tokenType'])
def _set_embed_config(self, access_token, embed_url, token_type=0, token_expiration=0):
"""
TODO: Add docstring
"""
self._embed_config = {
'type': 'report',
'accessToken': access_token,
'embedUrl': embed_url,
'tokenType': token_type,
'tokenExpiration': token_expiration
}
self._embedded = False
def set_dimensions(self, container_height, container_width):
"""Set width and height of Power BI report in px
Args:
container_height (number): report height
container_width (number): report width
"""
self.container_height = container_height
self.container_width = container_width
def export_visual_data(self, page_name, visual_name, rows=10, underlying_data=False):
"""Returns the data of given visual of the embedded Power BI report
Args:
page_name (string): Page name of the embedded report
visual_name (string): Visual's unique name
rows (int, optional): Number of rows of data. Defaults to 10
underlying_data (boolean, optional): Choice to show the underlying data or not. Default is False.
Returns:
string: visual's exported data
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
# Start exporting data on client side
self._export_visual_data_request = {
'pageName': page_name,
'visualName': visual_name,
'rows': rows,
'underlyingData': underlying_data
}
# Check if ipython kernel is available
if get_ipython():
# Wait for client-side to send visual data
with ui_events() as ui_poll:
# While visual data is not received
while self._visual_data == self.VISUAL_DATA_DEFAULT_STATE:
ui_poll(self.PROCESS_EVENTS_ITERATION)
time.sleep(self.POLLING_INTERVAL)
exported_data = self._visual_data
# Reset the _export_visual_data_request and _visual_data's value
self._export_visual_data_request = dict(self.EXPORT_VISUAL_DATA_REQUEST_DEFAULT_STATE)
self._visual_data = self.VISUAL_DATA_DEFAULT_STATE
return exported_data
def on(self, event, callback):
"""Register a callback to execute when the report emits the target event
Parameters
Args:
event (string): Name of Power BI event (eg. 'loaded', 'rendered', 'error')
callback (function): User defined function. Callback function is invoked with event details as parameter
"""
# TODO: Value of event should be from one of the Report.allowedEvents array
self._registered_event_handlers[event] = callback
def get_event_data(change):
event_data = change.new
event_name = event_data['event_name']
event_details = event_data['event_details']
# Do not invoke callback when _event_data trait is reset
if event_name is None:
return
# Check if a handler is registered for the current event
if event_name not in self._registered_event_handlers:
return
event_handler = self._registered_event_handlers[event_name]
event_handler(event_details)
# Reset the _event_data trait, so as to receive next event
self._event_data = dict(self.EVENT_DATA_DEFAULT_STATE)
# Check if already observing events
if not self._observing_events:
# Prevents calling DOMWidget.observe() again
self._observing_events = True
# Start observing Power BI events
self.observe(get_event_data, '_event_data')
def update_filters(self, filters):
"""Set report level filters in the embedded report.
Currently supports models.FiltersOperations.Add
Args:
filters ([models.ReportLevelFilters]): List of report level filters
Raises:
Exception: When report is not embedded
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
self._report_filters_request = {
'filters': filters,
'request_completed': False
}
# TODO: Should we wait for the filters to be applied
def remove_filters(self):
"""Remove all report level filters from the embedded report
Raises:
Exception: When report is not embedded
"""
self.update_filters([])
def get_pages(self):
"""Returns the list of pages of the embedded Power BI report
Returns:
string: list of pages
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
# Start getting pages on client side
self._get_pages_request = True
# Check if ipython kernel is available
if get_ipython():
# Wait for client-side to send list of pages
with ui_events() as ui_poll:
# While list of report pages is not received
while self._report_pages == self.REPORT_PAGES_DEFAULT_STATE:
ui_poll(self.PROCESS_EVENTS_ITERATION)
time.sleep(self.POLLING_INTERVAL)
pages = self._report_pages
# Reset the get_pages_request and report_pages's value
self._get_pages_request = bool(self.GET_PAGES_REQUEST_DEFAULT_STATE)
self._report_pages = list(self.REPORT_PAGES_DEFAULT_STATE)
return pages
def visuals_on_page(self, page_name):
"""Returns the list of visuals of the given page of the embedded Power BI report
Args:
page_name (string): Page name of the embedded report
Returns:
string: list of visuals
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
# Start getting visuals on client side
self._get_visuals_page_name = page_name
# Check if ipython kernel is available
if get_ipython():
# Wait for client-side to send list of visuals
with ui_events() as ui_poll:
# While list of visuals is not received
while self._page_visuals == self.PAGE_VISUALS_DEFAULT_STATE:
ui_poll(self.PROCESS_EVENTS_ITERATION)
time.sleep(self.POLLING_INTERVAL)
visuals = self._page_visuals
# Reset the get_visuals_page_name and page_visuals's value
self._get_visuals_page_name = self.GET_VISUALS_DEFAULT_PAGE_NAME
self._page_visuals = list(self.PAGE_VISUALS_DEFAULT_STATE)
return visuals
def set_bookmark(self, bookmark_name):
"""Applies a bookmark by name on the embedded report.
Args:
bookmark_name (string) : Bookmark Name
Raises:
Exception: When report is not embedded
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
self._report_bookmark_name = bookmark_name
def get_bookmarks(self):
"""Returns the list of bookmarks of the embedded Power BI report
Returns:
list: list of bookmarks
Raises:
Exception: When report is not embedded
"""
if self._embedded == False:
raise Exception(self.REPORT_NOT_EMBEDDED_MESSAGE)
# Start getting bookmarks on client side
self._get_bookmarks_request = True
# Check if ipython kernel is available
if get_ipython():
# Wait for client-side to send list of bookmarks
with ui_events() as ui_poll:
# While list of report bookmark(s) is not received
while self._report_bookmarks == self.REPORT_BOOKMARKS_DEFAULT_STATE:
ui_poll(self.PROCESS_EVENTS_ITERATION)
time.sleep(self.POLLING_INTERVAL)
bookmarks = self._report_bookmarks
if bookmarks == ['']:
bookmarks = self.REPORT_BOOKMARKS_DEFAULT_STATE
# Reset the _get_bookmarks_request and _report_bookmarks values
self._get_bookmarks_request = bool(self.GET_BOOKMARKS_REQUEST_DEFAULT_STATE)
self._report_bookmarks = list(self.REPORT_BOOKMARKS_DEFAULT_STATE)
return bookmarks

Просмотреть файл

Просмотреть файл

@ -1,54 +1,54 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
import pytest
from ipykernel.comm import Comm
from ipywidgets import Widget
class MockComm(Comm):
"""A mock Comm object.
Can be used to inspect calls to Comm's open/send/close methods.
"""
comm_id = 'a-b-c-d'
kernel = 'Truthy'
def __init__(self, *args, **kwargs):
self.log_open = []
self.log_send = []
self.log_close = []
super(MockComm, self).__init__(*args, **kwargs)
def open(self, *args, **kwargs):
self.log_open.append((args, kwargs))
def send(self, *args, **kwargs):
self.log_send.append((args, kwargs))
def close(self, *args, **kwargs):
self.log_close.append((args, kwargs))
_widget_attrs = {}
undefined = object()
@pytest.fixture
def mock_comm():
_widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined)
Widget._comm_default = lambda self: MockComm()
_widget_attrs['_ipython_display_'] = Widget._ipython_display_
def raise_not_implemented(*args, **kwargs):
raise NotImplementedError()
Widget._ipython_display_ = raise_not_implemented
yield MockComm()
for attr, value in _widget_attrs.items():
if value is undefined:
delattr(Widget, attr)
else:
setattr(Widget, attr, value)
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
import pytest
from ipykernel.comm import Comm
from ipywidgets import Widget
class MockComm(Comm):
"""A mock Comm object.
Can be used to inspect calls to Comm's open/send/close methods.
"""
comm_id = 'a-b-c-d'
kernel = 'Truthy'
def __init__(self, *args, **kwargs):
self.log_open = []
self.log_send = []
self.log_close = []
super(MockComm, self).__init__(*args, **kwargs)
def open(self, *args, **kwargs):
self.log_open.append((args, kwargs))
def send(self, *args, **kwargs):
self.log_send.append((args, kwargs))
def close(self, *args, **kwargs):
self.log_close.append((args, kwargs))
_widget_attrs = {}
undefined = object()
@pytest.fixture
def mock_comm():
_widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined)
Widget._comm_default = lambda self: MockComm()
_widget_attrs['_ipython_display_'] = Widget._ipython_display_
def raise_not_implemented(*args, **kwargs):
raise NotImplementedError()
Widget._ipython_display_ = raise_not_implemented
yield MockComm()
for attr, value in _widget_attrs.items():
if value is undefined:
delattr(Widget, attr)
else:
setattr(Widget, attr, value)

Просмотреть файл

@ -1,15 +1,15 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
def test_nbextension_path():
# Check that magic function can be imported from package root:
from powerbi_client import _jupyter_nbextension_paths
# Ensure that it can be called without incident:
path = _jupyter_nbextension_paths()
# Some sanity checks:
assert len(path) == 1
assert isinstance(path[0], dict)
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
def test_nbextension_path():
# Check that magic function can be imported from package root:
from powerbiclient import _jupyter_nbextension_paths
# Ensure that it can be called without incident:
path = _jupyter_nbextension_paths()
# Some sanity checks:
assert len(path) == 1
assert isinstance(path[0], dict)

Просмотреть файл

@ -1,267 +1,267 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
import pytest
from ..report import Report
ACCESS_TOKEN = 'dummy_access_token'
EMBED_URL = 'dummy_embed_url'
PAGE_NAME = 'dummy_page_name'
VISUAL_NAME = 'dummy_visual_name'
VISUAL_DATA = 'dummy_visual_data'
VISUAL_DATA_ROWS = 20
DEFAULT_DATA_ROWS = 10
REPORT_PAGES = ['dummy_report_pages']
PAGE_VISUALS = ['dummy_page_visuals']
REPORT_BOOKMARKS = ['dummy_report_bookmarks']
class TestCommAndTraitlets:
def test_sending_message(self, mock_comm):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report.comm = mock_comm
new_height = 450
new_width = 800
# Act
report.set_dimensions(new_height, new_width)
# Assert that comm sends all traitlet changes to frontend
assert mock_comm.log_send[0][1]['data']['state'] == {
'container_height': new_height
}
assert mock_comm.log_send[1][1]['data']['state'] == {
'container_width': new_width
}
class TestReportConstructor:
def test_report_constructor(self):
# Act
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Assert
assert report._embed_config == {
'type': 'report',
'accessToken': ACCESS_TOKEN,
'embedUrl': EMBED_URL,
'tokenType': 0,
'tokenExpiration': 0
}
assert report._embedded == False
def test_report_constructor_token_type(self):
# Act
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL, token_type=1)
# Assert
assert report._embed_config == {
'type': 'report',
'accessToken': ACCESS_TOKEN,
'embedUrl': EMBED_URL,
'tokenType': 1,
'tokenExpiration': 0
}
class TestSettingNewEmbedConfig:
def test_set_embed_config(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Simulate that report was earlier embedded
report._embedded = True
new_access_token = "new_dummy_access_token"
new_embed_url = 'new_dummy_embed_url'
# Act
report.set_embed_config(access_token=new_access_token, embed_url=new_embed_url)
# Assert
assert report._embed_config == {
'type': 'report',
'accessToken': new_access_token,
'embedUrl': new_embed_url,
'tokenType': 0,
'tokenExpiration': 0
}
assert report._embedded == False
class TestChangingNewReportSize:
def test_change_size(self):
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
new_height = 500
new_width = 900
report.set_dimensions(new_height, new_width)
# Assert traitlets are updated
assert report.container_height == new_height
assert report.container_width == new_width
class TestEventHandlers:
def test_setting_event_handler(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = True
event_name = 'loaded'
# Act
def loaded_callback():
pass
report.on(event_name, loaded_callback)
# Assert
assert event_name in report._registered_event_handlers.keys()
assert report._registered_event_handlers[event_name] == loaded_callback
assert report._observing_events == True
def test_setting_event_handler_again(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = True
event_name = 'loaded'
# Act
def loaded_callback():
# Dummy callback
pass
def loaded_callback2():
# Dummy callback
pass
report.on(event_name, loaded_callback)
report.on(event_name, loaded_callback2)
# Assert
assert event_name in report._registered_event_handlers.keys()
# Check new handler is registered and old one is unregistered
assert report._registered_event_handlers[event_name] == loaded_callback2
assert report._observing_events == True
def test_not_setting_event_handler(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Act
# Does not set any handler
# Assert
assert 'loaded' not in report._registered_event_handlers
class TestExportData:
def test_throws_when_not_embedded(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = False
# Act + Assert
with pytest.raises(Exception):
report.export_visual_data('page', 'visual')
def test_returned_data(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Data sent by frontend (Setting this upfront will prevent extract_data from waiting for data)
report.visual_data = VISUAL_DATA
report._embedded = True
# Act
returned_data = report.export_visual_data(PAGE_NAME, VISUAL_NAME, VISUAL_DATA_ROWS)
# Assert
assert returned_data == VISUAL_DATA
assert report.export_visual_data_request == report.EXPORT_VISUAL_DATA_REQUEST_DEFAULT_STATE
assert report.visual_data == report.VISUAL_DATA_DEFAULT_STATE
class TestGetPages:
def test_throws_when_not_embedded(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = False
# Act + Assert
with pytest.raises(Exception):
report.get_pages()
def test_returned_data(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Data sent by frontend (Setting this upfront will prevent get_pages from waiting for list of pages)
report._report_pages = REPORT_PAGES
report._embedded = True
# Act
returned_pages = report.get_pages()
# Assert
assert returned_pages == REPORT_PAGES
assert report._get_pages_request == report.GET_PAGES_REQUEST_DEFAULT_STATE
assert report._report_pages == report.REPORT_PAGES_DEFAULT_STATE
class TestGetVisuals:
def test_throws_when_not_embedded(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = False
# Act + Assert
with pytest.raises(Exception):
report.get_visuals('page')
def test_returned_data(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Data sent by frontend (Setting this upfront will prevent get_pages from waiting for list of pages)
report._page_visuals = PAGE_VISUALS
report._embedded = True
# Act
returned_visuals = report.visuals_on_page(PAGE_NAME)
# Assert
assert returned_visuals == PAGE_VISUALS
assert report._get_visuals_page_name == report.GET_VISUALS_DEFAULT_PAGE_NAME
assert report._page_visuals == report.PAGE_VISUALS_DEFAULT_STATE
class TestGetBookmarks:
def test_throws_when_not_embedded(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = False
# Act + Assert
with pytest.raises(Exception):
report.get_bookmarks()
def test_returned_data(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Data sent by frontend (Setting this upfront will prevent get_bookmarks from waiting for list of bookmarks)
report._report_bookmarks = REPORT_BOOKMARKS
report._embedded = True
# Act
returned_bookmarks = report.get_bookmarks()
# Assert
assert returned_bookmarks == REPORT_BOOKMARKS
assert report._get_bookmarks_request == report.GET_BOOKMARKS_REQUEST_DEFAULT_STATE
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
import pytest
from ..report import Report
ACCESS_TOKEN = 'dummy_access_token'
EMBED_URL = 'dummy_embed_url'
PAGE_NAME = 'dummy_page_name'
VISUAL_NAME = 'dummy_visual_name'
VISUAL_DATA = 'dummy_visual_data'
VISUAL_DATA_ROWS = 20
DEFAULT_DATA_ROWS = 10
REPORT_PAGES = ['dummy_report_pages']
PAGE_VISUALS = ['dummy_page_visuals']
REPORT_BOOKMARKS = ['dummy_report_bookmarks']
class TestCommAndTraitlets:
def test_sending_message(self, mock_comm):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report.comm = mock_comm
new_height = 450
new_width = 800
# Act
report.set_dimensions(new_height, new_width)
# Assert that comm sends all traitlet changes to frontend
assert mock_comm.log_send[0][1]['data']['state'] == {
'container_height': new_height
}
assert mock_comm.log_send[1][1]['data']['state'] == {
'container_width': new_width
}
class TestReportConstructor:
def test_report_constructor(self):
# Act
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Assert
assert report._embed_config == {
'type': 'report',
'accessToken': ACCESS_TOKEN,
'embedUrl': EMBED_URL,
'tokenType': 0,
'tokenExpiration': 0
}
assert report._embedded == False
def test_report_constructor_token_type(self):
# Act
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL, token_type=1)
# Assert
assert report._embed_config == {
'type': 'report',
'accessToken': ACCESS_TOKEN,
'embedUrl': EMBED_URL,
'tokenType': 1,
'tokenExpiration': 0
}
class TestSettingNewEmbedConfig:
def test_set_embed_config(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Simulate that report was earlier embedded
report._embedded = True
new_access_token = "new_dummy_access_token"
new_embed_url = 'new_dummy_embed_url'
# Act
report._set_embed_config(access_token=new_access_token, embed_url=new_embed_url)
# Assert
assert report._embed_config == {
'type': 'report',
'accessToken': new_access_token,
'embedUrl': new_embed_url,
'tokenType': 0,
'tokenExpiration': 0
}
assert report._embedded == False
class TestChangingNewReportSize:
def test_change_size(self):
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
new_height = 500
new_width = 900
report.set_dimensions(new_height, new_width)
# Assert traitlets are updated
assert report.container_height == new_height
assert report.container_width == new_width
class TestEventHandlers:
def test_setting_event_handler(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = True
event_name = 'loaded'
# Act
def loaded_callback():
pass
report.on(event_name, loaded_callback)
# Assert
assert event_name in report._registered_event_handlers.keys()
assert report._registered_event_handlers[event_name] == loaded_callback
assert report._observing_events == True
def test_setting_event_handler_again(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = True
event_name = 'loaded'
# Act
def loaded_callback():
# Dummy callback
pass
def loaded_callback2():
# Dummy callback
pass
report.on(event_name, loaded_callback)
report.on(event_name, loaded_callback2)
# Assert
assert event_name in report._registered_event_handlers.keys()
# Check new handler is registered and old one is unregistered
assert report._registered_event_handlers[event_name] == loaded_callback2
assert report._observing_events == True
def test_not_setting_event_handler(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Act
# Does not set any handler
# Assert
assert 'loaded' not in report._registered_event_handlers
class TestExportData:
def test_throws_when_not_embedded(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = False
# Act + Assert
with pytest.raises(Exception):
report.export_visual_data('page', 'visual')
def test_returned_data(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Data sent by frontend (Setting this upfront will prevent extract_data from waiting for data)
report._visual_data = VISUAL_DATA
report._embedded = True
# Act
returned_data = report.export_visual_data(PAGE_NAME, VISUAL_NAME, VISUAL_DATA_ROWS)
# Assert
assert returned_data == VISUAL_DATA
assert report._export_visual_data_request == report.EXPORT_VISUAL_DATA_REQUEST_DEFAULT_STATE
assert report._visual_data == report.VISUAL_DATA_DEFAULT_STATE
class TestGetPages:
def test_throws_when_not_embedded(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = False
# Act + Assert
with pytest.raises(Exception):
report.get_pages()
def test_returned_data(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Data sent by frontend (Setting this upfront will prevent get_pages from waiting for list of pages)
report._report_pages = REPORT_PAGES
report._embedded = True
# Act
returned_pages = report.get_pages()
# Assert
assert returned_pages == REPORT_PAGES
assert report._get_pages_request == report.GET_PAGES_REQUEST_DEFAULT_STATE
assert report._report_pages == report.REPORT_PAGES_DEFAULT_STATE
class TestGetVisuals:
def test_throws_when_not_embedded(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = False
# Act + Assert
with pytest.raises(Exception):
report.get_visuals('page')
def test_returned_data(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Data sent by frontend (Setting this upfront will prevent get_pages from waiting for list of pages)
report._page_visuals = PAGE_VISUALS
report._embedded = True
# Act
returned_visuals = report.visuals_on_page(PAGE_NAME)
# Assert
assert returned_visuals == PAGE_VISUALS
assert report._get_visuals_page_name == report.GET_VISUALS_DEFAULT_PAGE_NAME
assert report._page_visuals == report.PAGE_VISUALS_DEFAULT_STATE
class TestGetBookmarks:
def test_throws_when_not_embedded(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
report._embedded = False
# Act + Assert
with pytest.raises(Exception):
report.get_bookmarks()
def test_returned_data(self):
# Arrange
report = Report(access_token=ACCESS_TOKEN, embed_url=EMBED_URL)
# Data sent by frontend (Setting this upfront will prevent get_bookmarks from waiting for list of bookmarks)
report._report_bookmarks = REPORT_BOOKMARKS
report._embedded = True
# Act
returned_bookmarks = report.get_bookmarks()
# Assert
assert returned_bookmarks == REPORT_BOOKMARKS
assert report._get_bookmarks_request == report.GET_BOOKMARKS_REQUEST_DEFAULT_STATE
assert report._report_bookmarks == report.REPORT_BOOKMARKS_DEFAULT_STATE

Просмотреть файл

@ -1,4 +1,4 @@
[pytest]
testpaths = powerbi_client/tests examples
testpaths = powerbiclient/tests examples
norecursedirs = node_modules .ipynb_checkpoints
addopts = --nbval --current-env

Просмотреть файл

@ -19,7 +19,7 @@ from setuptools import setup
# The name of the project
name = 'powerbi_client'
name = 'powerbiclient'
# Ensure a valid python version
ensure_python('>=3.4')
@ -44,10 +44,10 @@ package_data_spec = {
}
data_files_spec = [
('share/jupyter/nbextensions/powerbi_client',
('share/jupyter/nbextensions/powerbiclient',
nb_path, '*.js*'),
('share/jupyter/lab/extensions', lab_path, '*.tgz'),
('etc/jupyter/nbconfig/notebook.d' , HERE, 'powerbi_client.json')
('etc/jupyter/nbconfig/notebook.d' , HERE, 'powerbiclient.json')
]

Просмотреть файл

@ -10,6 +10,6 @@
// dynamically.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(window as any).__webpack_public_path__ =
document.querySelector('body')!.getAttribute('data-base-url') + 'nbextensions/powerbi_client';
document.querySelector('body')!.getAttribute('data-base-url') + 'nbextensions/powerbiclient';
export * from './index';

Просмотреть файл

@ -52,8 +52,8 @@ export class ReportModel extends DOMWidgetModel {
_embedded: false,
container_height: 0,
container_width: 0,
export_visual_data_request: {},
visual_data: null,
_export_visual_data_request: {},
_visual_data: null,
_event_data: {
event_name: null,
event_details: null,
@ -124,17 +124,13 @@ export class ReportView extends DOMWidgetView {
this.reportContainer = newDivElement;
this.embed_configChanged();
this.embedConfigChanged();
// Observe changes in the traitlets in Python, and define custom callback.
this.model.on('change:_embed_config', this.embed_configChanged, this);
this.model.on('change:_embed_config', this.embedConfigChanged, this);
this.model.on('change:container_height', this.dimensionsChanged, this);
this.model.on('change:container_width', this.dimensionsChanged, this);
this.model.on(
'change:export_visual_data_request',
this.export_visual_data_requestChanged,
this
);
this.model.on('change:_export_visual_data_request', this.exportVisualDataRequestChanged, this);
this.model.on('change:_report_filters_request', this.reportFiltersChanged, this);
this.model.on('change:_get_pages_request', this.getPagesRequestChanged, this);
this.model.on('change:_get_visuals_page_name', this.getVisualsPageNameChanged, this);
@ -152,7 +148,7 @@ export class ReportView extends DOMWidgetView {
this.touch();
}
embed_configChanged(): void {
embedConfigChanged(): void {
const embedConfig = this.model.get('_embed_config');
const reportConfig = embedConfig as IReportEmbedConfiguration;
@ -249,31 +245,31 @@ export class ReportView extends DOMWidgetView {
this.touch();
}
async export_visual_data_requestChanged(): Promise<void> {
async exportVisualDataRequestChanged(): Promise<void> {
if (!this.report) {
console.error(REPORT_NOT_EMBEDDED_MESSAGE);
return;
}
const export_visual_data_request = this.model.get(
'export_visual_data_request'
const exportVisualDataRequest = this.model.get(
'_export_visual_data_request'
) as ExportVisualDataRequest;
// Check export visual data request object is null or empty
if (!export_visual_data_request || Object.keys(export_visual_data_request).length === 0) {
if (!exportVisualDataRequest || Object.keys(exportVisualDataRequest).length === 0) {
// This is the case of model reset
return;
}
if (!export_visual_data_request.pageName || !export_visual_data_request.visualName) {
if (!exportVisualDataRequest.pageName || !exportVisualDataRequest.visualName) {
console.error('Page and visual names are required');
return;
}
const pageName = export_visual_data_request.pageName;
const visualName = export_visual_data_request.visualName;
const dataRows = export_visual_data_request.rows;
const exportDataType = export_visual_data_request.underlyingData
const pageName = exportVisualDataRequest.pageName;
const visualName = exportVisualDataRequest.visualName;
const dataRows = exportVisualDataRequest.rows;
const exportDataType = exportVisualDataRequest.underlyingData
? models.ExportDataType.Underlying
: models.ExportDataType.Summarized;
@ -291,7 +287,7 @@ export class ReportView extends DOMWidgetView {
const data = await selectedVisual.exportData(exportDataType, dataRows);
// Update data
this.model.set('visual_data', data.data);
this.model.set('_visual_data', data.data);
this.touch();
} catch (error) {
console.error('Export visual data error:', error);

Просмотреть файл

@ -27,7 +27,7 @@ module.exports = [
entry: './src/extension.ts',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'powerbi_client', 'nbextension', 'static'),
path: path.resolve(__dirname, 'powerbiclient', 'nbextension', 'static'),
libraryTarget: 'amd'
},
module: {