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:
Родитель
29763851f9
Коммит
b85f8949a9
|
@ -1,2 +1,2 @@
|
|||
[run]
|
||||
omit = powerbi_client/tests/*
|
||||
omit = powerbiclient/tests/*
|
||||
|
|
|
@ -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
|
||||
|
|
14
README.md
14
README.md
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
6
setup.py
6
setup.py
|
@ -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: {
|
||||
|
|
Загрузка…
Ссылка в новой задаче