Merged PR 341985: [React Wrapper]: Update folder structure of wrapper

Updated the folder structure of wrapper to align with Angular wrapper.
The new folder structure is React (folder) > demo and powerbi-client-react (folders)

Update log:

1. Updated the pipeline scripts and YAML files to match with the new structure.
2. package.json (both demo and wrapper) to update scripts with demo folder location

Added log:
1. README.md in wrapper folder
2. `.editor.config` in React folder
3. NOTICE.txt in React folder
4. CODE_OF_CONDUCT.md in root folder
5. owners.txt in root folder

Related work items: #986256, #992684, #993295
This commit is contained in:
Karan Dewani 2023-02-02 09:42:56 +00:00
Родитель 6a40341f1e
Коммит 41053c282d
42 изменённых файлов: 15995 добавлений и 1086 удалений

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

@ -5,11 +5,11 @@
**/package-lock.json
# testing
/coverage
/compiledTests
coverage
compiledTests
# production
/dist
dist
# misc
.DS_Store

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

@ -73,7 +73,7 @@ extends:
- task: npmAuthenticate@0
displayName: 'NPM Authenticate'
inputs:
workingFile: '$(Build.SourcesDirectory)\.npmrc'
workingFile: '$(Build.SourcesDirectory)\React\powerbi-client-react\.npmrc'
- task: PowerShell@2
displayName: 'Restore'
@ -102,12 +102,22 @@ extends:
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
dist\**\*
React\powerbi-client-react\dist\**\*
LICENSE.txt
package.json
React\powerbi-client-react\package.json
README.md
TargetFolder: '$(OUTPUTROOT)\outputs\build\build_artifacts'
# Copying Readme and License to the powerbi-client-react folder
- task: CopyFiles@2
displayName: 'Copy Readme and License to powerbi-client-react folder'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
README.md
LICENSE.txt
TargetFolder: '$(OUTPUTROOT)\outputs\build\build_artifacts\React\powerbi-client-react'
# Copying source artifacts to the Output folder
- task: CopyFiles@2
displayName: 'Copy Source to Output folder'
@ -131,9 +141,9 @@ extends:
- task: CopyFiles@2
displayName: 'Copy tgz-package to Output folder'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
SourceFolder: '$(Build.SourcesDirectory)\React\powerbi-client-react'
Contents: |
*.tgz
**\*.tgz
TargetFolder: '$(OUTPUTROOT)\outputs\package\tgz-package'
# Signing all the output files

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

@ -76,7 +76,7 @@ extends:
- task: npmAuthenticate@0
displayName: 'NPM Authenticate'
inputs:
workingFile: '$(Build.SourcesDirectory)\.npmrc'
workingFile: '$(Build.SourcesDirectory)\React\powerbi-client-react\.npmrc'
- task: PowerShell@2
displayName: 'Restore'
@ -105,12 +105,22 @@ extends:
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
dist\**\*
React\powerbi-client-react\dist\**\*
LICENSE.txt
package.json
React\powerbi-client-react\package.json
README.md
TargetFolder: '$(OUTPUTROOT)\outputs\build\build_artifacts'
# Copying Readme and License to the powerbi-client-react folder
- task: CopyFiles@2
displayName: 'Copy Readme and License to powerbi-client-react folder'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
README.md
LICENSE.txt
TargetFolder: '$(OUTPUTROOT)\outputs\build\build_artifacts\React\powerbi-client-react'
# Copying source artifacts to the Output folder
- task: CopyFiles@2
displayName: 'Copy Source to Output folder'
@ -134,9 +144,9 @@ extends:
- task: CopyFiles@2
displayName: 'Copy tgz-package to Output folder'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
SourceFolder: '$(Build.SourcesDirectory)\React\powerbi-client-react'
Contents: |
*.tgz
**\*.tgz
TargetFolder: '$(OUTPUTROOT)\outputs\package\tgz-package'
# Signing the files created by the build and packaging script

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

@ -1,18 +1,12 @@
$exitCode = 0;
Write-Host "start: npm run build"
& cd .\React\powerbi-client-react
& npm run build
Write-Host "done: npm run build"
$exitCode += $LASTEXITCODE;
# Check linting
Write-Host "start: npm run lint"
& npm run lint
Write-Host "done: npm run lint"
$exitCode += $LASTEXITCODE;
Write-Host "start: Get dist folder files"
& dir "dist"
$hasAnySubdir = (Get-ChildItem -Force -Directory './dist').Count -gt 0

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

@ -1,11 +1,18 @@
$exitCode = 0;
$baseDir = $pwd;
Write-Host "start: npm pack"
& cd .\React\powerbi-client-react
& npm pack
Write-Host "done: npm pack"
$exitCode += $LASTEXITCODE;
if ($exitCode -ne 0) {
Write-Host "Failed to run npm pack"
exit $exitCode
}
Write-Host "start: Get content of current folder"
& dir
Write-Host "done: Get content of current folder"
@ -13,7 +20,7 @@ Write-Host "done: Get content of current folder"
$exitCode += $LASTEXITCODE;
Write-Host "start: test package"
& .\.pipelines\test_package.ps1
& $baseDir\.pipelines\test_package.ps1
Write-Host "done: test package"
$exitCode += $LASTEXITCODE;

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

@ -18,6 +18,7 @@ Write-Host "done: try install latest npm version"
# Do not update $exitCode because we do not want to fail if install latest npm version fails.
Write-Host "start: npm install"
& cd .\React\powerbi-client-react
& npm install --no-audit --no-save
Write-Host "done: npm install"
$exitCode += $LASTEXITCODE;

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

@ -1,6 +1,7 @@
$exitCode = 0;
Write-Host "start: npm run test"
& cd .\React\powerbi-client-react
& npm run test
Write-Host "done: npm run test"

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

@ -1,3 +1,4 @@
cd .\React\powerbi-client-react
try {
# package.json is in root folder, while version.ps1 runs in .pipelines folder.
$version = (Get-Content "package.json") -join "`n" | ConvertFrom-Json | Select -ExpandProperty "version"
@ -5,7 +6,7 @@ try {
Write-Host "Build Number is" $buildNumber
Write-Host "##vso[task.setvariable variable=CustomBuildNumber]${buildNumber}" # This will allow you to use it from env var in later steps of the same phase
Write-Host "##vso[task.setvariable variable=CustomBuildNumber]${buildNumber}" # This will allow you to use it from env var in later steps of the same phase
}
catch {
Write-Error $_.Exception

9
CODE_OF_CONDUCT.md Normal file
Просмотреть файл

@ -0,0 +1,9 @@
# Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns

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

@ -9,6 +9,11 @@ git clone <url>
Navigate to the cloned directory
Navigate to the React\powerbi-client-react workspace folder:
```
cd React\powerbi-client-react
```
Install local dependencies:
```
npm install
@ -24,14 +29,13 @@ Or if using VScode: `Ctrl + Shift + B`
```
npm test
```
By default the tests run using PhantomJS browser
By default the tests run using ChromeHeadless browser
The build and tests use webpack to compile all the source modules into bundled module that can be executed in the browser.
## Running the demo
```
npm run install:demo
npm run demo
```
@ -40,4 +44,4 @@ Open the address to view in the browser:
http://localhost:8080/
## Flow Diagram for the PowerBIEmbed Component:
![Flow Diagram](https://github.com/microsoft/powerbi-client-react/raw/master/resources/react_wrapper_flow_diagram.png)
![Flow Diagram](/resources/react_wrapper_flow_diagram.png)

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

@ -1,3 +1,5 @@
powerbi-client-react
Copyright (c) Microsoft Corporation.
MIT License

120
README.md
Просмотреть файл

@ -1,5 +1,5 @@
# powerbi-client-react
Power BI React component. This library lets you embed Power BI report, dashboard, dashboard tile, report visual, or Q&A in your React application.
Power BI React component. This library lets you embed Power BI report, dashboard, dashboard tile, report visual, Q&A or paginated report in your React application.
## Quick Start
@ -13,11 +13,11 @@ import { PowerBIEmbed } from 'powerbi-client-react';
```jsx
<PowerBIEmbed
embedConfig = {{
type: 'report', // Supported types: report, dashboard, tile, visual and qna
type: 'report', // Supported types: report, dashboard, tile, visual, qna and paginated report
id: '<Report Id>',
embedUrl: '<Embed Url>',
accessToken: '<Access Token>',
tokenType: models.TokenType.Embed,
tokenType: models.TokenType.Embed, // Use models.TokenType.Aad for SaaS embed
settings: {
panes: {
filters: {
@ -29,15 +29,17 @@ import { PowerBIEmbed } from 'powerbi-client-react';
}
}}
eventHandlers = {
eventHandlers = {
new Map([
['loaded', function () {console.log('Report loaded');}],
['rendered', function () {console.log('Report rendered');}],
['error', function (event) {console.log(event.detail);}]
['error', function (event) {console.log(event.detail);}],
['visualClicked', () => console.log('visual clicked')],
['pageChanged', (event) => console.log(event)],
])
}
cssClassName = { "report-style-class" }
cssClassName = { "reportClass" }
getEmbeddedComponent = { (embeddedReport) => {
this.report = embeddedReport as Report;
@ -45,30 +47,31 @@ import { PowerBIEmbed } from 'powerbi-client-react';
/>
```
### How to [bootstrap a PowerBI report](https://aka.ms/PbieBootstrap):
### How to [bootstrap a PowerBI report](https://learn.microsoft.com/javascript/api/overview/powerbi/bootstrap-better-performance):
```jsx
<PowerBIEmbed
embedConfig = {{
type: 'report', // Supported types: report, dashboard, tile, visual and qna
id: undefined,
type: 'report', // Supported types: report, dashboard, tile, visual, qna and paginated report
id: undefined,
embedUrl: undefined,
accessToken: undefined, // Keep as empty string, null or undefined
tokenType: models.TokenType.Embed
}}
/>
```
__Note__: To embed the report after bootstrap, update the props (with atleast accessToken).
__Note__: To embed the report after bootstrap, update the props (with at least accessToken).
### Demo
A React application that embeds a sample report using the _PowerBIEmbed_ component.<br/>
It demonstrates the complete flow from bootstrapping the report, to embedding and updating the embedded report.<br/>
It also demonstrates the usage of _powerbi report authoring_ library by deleting a visual from report on click of "Delete a Visual" button.
This demo includes a React application that demonstrates the complete flow of embedding a sample report using the PowerBIEmbed component.
The demo shows how to bootstrap the report, embed it, and update it. Additionally, the demo showcases the usage of the powerbi report authoring library by enabling the user to change the type of visual from a report using the "Change visual type" button.
The demo also sets a "DataSelected" event, which allows the user to interact with the embedded report and retrieve information about the selected data.
To run the demo on localhost, run the following commands:
```
npm run install:demo
npm run demo
```
@ -77,18 +80,18 @@ Redirect to http://localhost:8080/ to view in the browser.
### Usage
|Use case|Details|
|:------|:------|
|Embed Power BI|To embed your powerbi artifact, pass the component with atleast _type_, _embedUrl_ and _accessToken_ in _embedConfig_ prop.|
|Embed Power BI|To embed your powerbi artifact, pass the component with at least _type_, _embedUrl_ and _accessToken_ in _embedConfig_ prop.|
|Get reference to the embedded object|Pass a callback method which accepts the embedded object as parameter to the _getEmbed_ of props.<br/>Refer to the _getEmbed_ prop in [Quick Start](#quick-start).|
|Apply style class|Pass the name(s) of style classes to be added to the embed container div to the _cssClassName_ props.|
|Set event handlers|Pass a map object of event name (string) and event handler (function) to the _eventHandlers_ prop. <br/>__Key__: Event name <br/>__Value__: Event handler method to be triggered<br/>Event handler method takes 2 optional params:<br/>First parameter: Event<br/>Second parameter: Reference to the embedded entity|
|Reset event handlers|To reset event handler for an event, set the event handler's value as `null` in the _eventHandlers_ map of props.|
|Set new accessToken|To set new accessToken in the same embedded powerbi artifact, pass the updated _accessToken_ in _embedConfig_ of props. <br/>Reload manually with report.reload() after providing new token if the current token in report has already expired<br/>Example scenario: _Current token has expired_.|
|Update settings (Report type only)|To update the report settings, update the _embedConfig.settings_ property of props.<br/>Refer to the _embedConfig.settings_ prop in [Quick Start](#quick-start).<br/>__Note__: Update the settings only by updating embedConfig prop|
|Bootstrap Power BI|To [bootstrap your powerbi entity](https://aka.ms/PbieBootstrap), pass the props to the component without _accessToken_ in _embedConfig_.<br/>__Note__: _embedConfig_ of props should atleast contain __type__ of the powerbi entity being embedded. <br/>Available types: "report", "dashboard", "tile", "visual" and "qna".<br/>Refer to _How to bootstrap a report_ section in [Quick Start](#quick-start).|
|Bootstrap Power BI|To [bootstrap your powerbi entity](https://learn.microsoft.com/javascript/api/overview/powerbi/bootstrap-better-performance), pass the props to the component without _accessToken_ in _embedConfig_.<br/>__Note__: _embedConfig_ of props should at least contain __type__ of the powerbi entity being embedded. <br/>Available types: "report", "dashboard", "tile", "visual", "qna" and "paginated report".<br/>Refer to _How to bootstrap a report_ section in [Quick Start](#quick-start).|
|Using with PowerBI Report Authoring|1. Install [powerbi-report-authoring](https://www.npmjs.com/package/powerbi-report-authoring) as npm dependency.<br>2. Use the report authoring APIs using the embedded report's instance|
|Phased embedding (Report type only)|Set phasedEmbedding prop's value as `true` <br/> Refer to [Phased embedding docs](https://github.com/microsoft/PowerBI-JavaScript/wiki/Phased-Embedding).|
|Phased embedding (Report type only)|Set phasedEmbedding prop's value as `true` <br/> Refer to [Phased embedding docs](https://learn.microsoft.com/javascript/api/overview/powerbi/phased-embedding).|
|Apply Filters (Report type only)|1. To apply updated filters, update filters in _embedConfig_ props.<br/>2. To remove the applied filters, update the _embedConfig_ prop with the filters removed or set as undefined/null.|
|Set Page (Report type only)|To set a page when embedding a report or on an embedded report, provide pageName field in the _embedConfig_.
|Set Page (Report type only)|To set a page when embedding a report or on an embedded report, provide pageName field in the _embedConfig_.
__Note__: To use this library in IE browser, use [react-app-polyfill](https://www.npmjs.com/package/react-app-polyfill) to add support for the incompatible features. Refer to the imports of [demo/index.tsx](https://github.com/microsoft/powerbi-client-react/blob/master/demo/index.tsx).
@ -122,12 +125,67 @@ interface EmbedProps {
// Provide instance of PowerBI service (optional)
service?: service.Service
}
type EventHandler = {
(event?: service.ICustomEvent<any>, embeddedEntity?: Embed): void | null;
};
```
## Supported Events
### Events supported by various Power BI entities:
|Entity|Event|
|:----- |:----- |
| Report | "buttonClicked", "commandTriggered", "dataHyperlinkClicked", "dataSelected", "loaded", "pageChanged", "rendered", "saveAsTriggered", "saved", "selectionChanged", "visualClicked", "visualRendered" |
| Dashboard | "loaded", "tileClicked" |
| Tile | "tileLoaded", "tileClicked" |
| QnA | "visualRendered" |
### Event Handler to be used with Map
```ts
type EventHandler = (event?: service.ICustomEvent<any>, embeddedEntity?: Embed) => void | null;
```
## Using supported SDK methods for Power BI artifacts
### Import
*Import the 'PowerBIEmbed' inside your targeted component file:*
```ts
import { PowerBIEmbed } from 'powerbi-client-react';
```
### Use
You can use ```report``` state to call supported SDK APIs.
Steps:
1. Create one state for storing the report object, for example, ```const [report, setReport] = useState<Report>();```.
2. Use the ```setReport``` method inside the component to set the report object.
<br />
```ts
<PowerBIEmbed
embedConfig = { sampleReportConfig }
eventHandlers = { eventHandlersMap }
cssClassName = { reportClass }
getEmbeddedComponent = { (embedObject: Embed) => {
setReport(embedObject as Report);
} }
/>
```
3. Once the report object is set, it can be used to call SDK methods such as ```getVisuals```, ```getBookmarks```, etc.
<br />
```ts
async getReportPages(): Page[] {
// this.report is a class variable, initialized in step 3
const activePage: Page | undefined = await report.getActivePage();
console.log(pages);
}
```
### Dependencies
[powerbi-client](https://www.npmjs.com/package/powerbi-client)
@ -136,10 +194,24 @@ type EventHandler = {
[react](https://www.npmjs.com/package/react)
### Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsofts Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-partys policies.
### Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit <https://cla.opensource.microsoft.com>.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments
### Data Collection.
The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications.
If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsofts privacy statement.
Our privacy statement is located at [Microsoft Privacy Statement](https://privacy.microsoft.com/privacystatement). You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.
### Support
Our public support page is available at [Microsoft Support Statement](https://powerbi.microsoft.com/support/).

16
React/.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

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

14779
React/NOTICE.txt Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -3,7 +3,7 @@
"version": "1.0.0",
"description": "Demo for usage of powerbi-client-react",
"scripts": {
"demo": "webpack-dev-server --static ./ --open"
"demo": "webpack-dev-server --static ./src/ --open"
},
"license": "MIT",
"dependencies": {

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

@ -1,3 +1,6 @@
/* Copyright (c) Microsoft Corporation.
Licensed under the MIT License. */
.report-container {
height: 75vh;
margin: 8px auto;

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

@ -1,256 +1,256 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useState } from 'react';
import { models, Report, Embed, service, Page } from 'powerbi-client';
import { IHttpPostMessageResponse } from 'http-post-message';
import { PowerBIEmbed } from 'powerbi-client-react';
import 'powerbi-report-authoring';
import { sampleReportUrl } from './public/constants';
import './DemoApp.css';
// Root Component to demonstrate usage of embedded component
function DemoApp (): JSX.Element {
// PowerBI Report object (to be received via callback)
const [report, setReport] = useState<Report>();
// Track Report embedding status
const [isEmbedded, setIsEmbedded] = useState<boolean>(false);
// Overall status message of embedding
const [displayMessage, setMessage] = useState(`The report is bootstrapped. Click the Embed Report button to set the access token`);
// CSS Class to be passed to the embedded component
const reportClass = 'report-container';
// Pass the basic embed configurations to the embedded component to bootstrap the report on first load
// Values for properties like embedUrl, accessToken and settings will be set on click of button
const [sampleReportConfig, setReportConfig] = useState<models.IReportEmbedConfiguration>({
type: 'report',
embedUrl: undefined,
tokenType: models.TokenType.Embed,
accessToken: undefined,
settings: undefined,
});
/**
* Map of event handlers to be applied to the embedded report
* Update event handlers for the report by redefining the map using the setEventHandlersMap function
* Set event handler to null if event needs to be removed
* More events can be provided from here
* https://docs.microsoft.com/en-us/javascript/api/overview/powerbi/handle-events#report-events
*/
const[eventHandlersMap, setEventHandlersMap] = useState<Map<string, (event?: service.ICustomEvent<any>, embeddedEntity?: Embed) => void | null>>(new Map([
['loaded', () => console.log('Report has loaded')],
['rendered', () => console.log('Report has rendered')],
['error', (event?: service.ICustomEvent<any>) => {
if (event) {
console.error(event.detail);
}
},
],
['visualClicked', () => console.log('visual clicked')],
['pageChanged', (event) => console.log(event)],
]));
/**
* Embeds report
*
* @returns Promise<void>
*/
const embedReport = async (): Promise<void> => {
console.log('Embed Report clicked');
// Get the embed config from the service
const reportConfigResponse = await fetch(sampleReportUrl);
if (reportConfigResponse === null) {
return;
}
if (!reportConfigResponse?.ok) {
console.error(`Failed to fetch config for report. Status: ${ reportConfigResponse.status } ${ reportConfigResponse.statusText }`);
return;
}
const reportConfig = await reportConfigResponse.json();
// Update the reportConfig to embed the PowerBI report
setReportConfig({
...sampleReportConfig,
embedUrl: reportConfig.EmbedUrl,
accessToken: reportConfig.EmbedToken.Token
});
setIsEmbedded(true);
// Update the display message
setMessage('Use the buttons above to interact with the report using Power BI Client APIs.');
};
/**
* Hide Filter Pane
*
* @returns Promise<IHttpPostMessageResponse<void> | undefined>
*/
const hideFilterPane = async (): Promise<IHttpPostMessageResponse<void> | undefined> => {
// Check if report is available or not
if (!report) {
setDisplayMessageAndConsole('Report not available');
return;
}
// New settings to hide filter pane
const settings = {
panes: {
filters: {
expanded: false,
visible: false,
},
},
};
try {
const response: IHttpPostMessageResponse<void> = await report.updateSettings(settings);
// Update display message
setDisplayMessageAndConsole('Filter pane is hidden.');
return response;
} catch (error) {
console.error(error);
return;
}
};
/**
* Set data selected event
*
* @returns void
*/
const setDataSelectedEvent = () => {
setEventHandlersMap(new Map<string, (event?: service.ICustomEvent<any>, embeddedEntity?: Embed) => void | null> ([
...eventHandlersMap,
['dataSelected', (event) => console.log(event)],
]));
setMessage('Data Selected event set successfully. Select data to see event in console.');
}
/**
* Change visual type
*
* @returns Promise<void>
*/
const changeVisualType = async (): Promise<void> => {
// Check if report is available or not
if (!report) {
setDisplayMessageAndConsole('Report not available');
return;
}
// Get active page of the report
const activePage: Page | undefined = await report.getActivePage();
if (!activePage) {
setMessage('No Active page found');
return;
}
try {
// Change the visual type using powerbi-report-authoring
// For more information: https://docs.microsoft.com/en-us/javascript/api/overview/powerbi/report-authoring-overview
const visual = await activePage.getVisualByName('VisualContainer6');
const response = await visual.changeType('lineChart');
setDisplayMessageAndConsole(`The ${visual.type} was updated to lineChart.`);
return response;
}
catch (error) {
if (error === 'PowerBIEntityNotFound') {
console.log('No Visual found with that name');
} else {
console.log(error);
}
}
};
/**
* Set display message and log it in the console
*
* @returns void
*/
const setDisplayMessageAndConsole = (message: string): void => {
setMessage(message);
console.log(message);
}
const controlButtons =
isEmbedded ?
<>
<button onClick = { changeVisualType }>
Change visual type</button>
<button onClick = { hideFilterPane }>
Hide filter pane</button>
<button onClick = { setDataSelectedEvent }>
Set event</button>
<label className = "display-message">
{ displayMessage }
</label>
</>
:
<>
<label className = "display-message position">
{ displayMessage }
</label>
<button onClick = { embedReport } className = "embed-report">
Embed Report</button>
</>;
const header =
<div className = "header">Power BI Embedded React Component Demo</div>;
const reportComponent =
<PowerBIEmbed
embedConfig = { sampleReportConfig }
eventHandlers = { eventHandlersMap }
cssClassName = { reportClass }
getEmbeddedComponent = { (embedObject: Embed) => {
console.log(`Embedded object of type "${ embedObject.embedtype }" received`);
setReport(embedObject as Report);
} }
/>;
const footer =
<div className = "footer">
<p>This demo is powered by Power BI Embedded Analytics</p>
<label className = "separator-pipe">|</label>
<img title = "Power-BI" alt = "PowerBI_Icon" className = "footer-icon" src = "./assets/PowerBI_Icon.png" />
<p>Explore our<a href = "https://aka.ms/pbijs/" target = "_blank" rel = "noreferrer noopener">Playground</a></p>
<label className = "separator-pipe">|</label>
<img title = "GitHub" alt = "GitHub_Icon" className = "footer-icon" src = "./assets/GitHub_Icon.png" />
<p>Find the<a href = "https://github.com/microsoft/PowerBI-client-react" target = "_blank" rel = "noreferrer noopener">source code</a></p>
</div>;
return (
<div className = "container">
{ header }
<div className = "controls">
{ controlButtons }
{ isEmbedded ? reportComponent : null }
</div>
{ footer }
</div>
);
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useState } from 'react';
import { models, Report, Embed, service, Page } from 'powerbi-client';
import { IHttpPostMessageResponse } from 'http-post-message';
import { PowerBIEmbed } from 'powerbi-client-react';
import 'powerbi-report-authoring';
import { sampleReportUrl } from './public/constants';
import './DemoApp.css';
// Root Component to demonstrate usage of embedded component
function DemoApp (): JSX.Element {
// PowerBI Report object (to be received via callback)
const [report, setReport] = useState<Report>();
// Track Report embedding status
const [isEmbedded, setIsEmbedded] = useState<boolean>(false);
// Overall status message of embedding
const [displayMessage, setMessage] = useState(`The report is bootstrapped. Click the Embed Report button to set the access token`);
// CSS Class to be passed to the embedded component
const reportClass = 'report-container';
// Pass the basic embed configurations to the embedded component to bootstrap the report on first load
// Values for properties like embedUrl, accessToken and settings will be set on click of button
const [sampleReportConfig, setReportConfig] = useState<models.IReportEmbedConfiguration>({
type: 'report',
embedUrl: undefined,
tokenType: models.TokenType.Embed,
accessToken: undefined,
settings: undefined,
});
/**
* Map of event handlers to be applied to the embedded report
* Update event handlers for the report by redefining the map using the setEventHandlersMap function
* Set event handler to null if event needs to be removed
* More events can be provided from here
* https://docs.microsoft.com/en-us/javascript/api/overview/powerbi/handle-events#report-events
*/
const[eventHandlersMap, setEventHandlersMap] = useState<Map<string, (event?: service.ICustomEvent<any>, embeddedEntity?: Embed) => void | null>>(new Map([
['loaded', () => console.log('Report has loaded')],
['rendered', () => console.log('Report has rendered')],
['error', (event?: service.ICustomEvent<any>) => {
if (event) {
console.error(event.detail);
}
},
],
['visualClicked', () => console.log('visual clicked')],
['pageChanged', (event) => console.log(event)],
]));
/**
* Embeds report
*
* @returns Promise<void>
*/
const embedReport = async (): Promise<void> => {
console.log('Embed Report clicked');
// Get the embed config from the service
const reportConfigResponse = await fetch(sampleReportUrl);
if (reportConfigResponse === null) {
return;
}
if (!reportConfigResponse?.ok) {
console.error(`Failed to fetch config for report. Status: ${ reportConfigResponse.status } ${ reportConfigResponse.statusText }`);
return;
}
const reportConfig = await reportConfigResponse.json();
// Update the reportConfig to embed the PowerBI report
setReportConfig({
...sampleReportConfig,
embedUrl: reportConfig.EmbedUrl,
accessToken: reportConfig.EmbedToken.Token
});
setIsEmbedded(true);
// Update the display message
setMessage('Use the buttons above to interact with the report using Power BI Client APIs.');
};
/**
* Hide Filter Pane
*
* @returns Promise<IHttpPostMessageResponse<void> | undefined>
*/
const hideFilterPane = async (): Promise<IHttpPostMessageResponse<void> | undefined> => {
// Check if report is available or not
if (!report) {
setDisplayMessageAndConsole('Report not available');
return;
}
// New settings to hide filter pane
const settings = {
panes: {
filters: {
expanded: false,
visible: false,
},
},
};
try {
const response: IHttpPostMessageResponse<void> = await report.updateSettings(settings);
// Update display message
setDisplayMessageAndConsole('Filter pane is hidden.');
return response;
} catch (error) {
console.error(error);
return;
}
};
/**
* Set data selected event
*
* @returns void
*/
const setDataSelectedEvent = () => {
setEventHandlersMap(new Map<string, (event?: service.ICustomEvent<any>, embeddedEntity?: Embed) => void | null> ([
...eventHandlersMap,
['dataSelected', (event) => console.log(event)],
]));
setMessage('Data Selected event set successfully. Select data to see event in console.');
}
/**
* Change visual type
*
* @returns Promise<void>
*/
const changeVisualType = async (): Promise<void> => {
// Check if report is available or not
if (!report) {
setDisplayMessageAndConsole('Report not available');
return;
}
// Get active page of the report
const activePage: Page | undefined = await report.getActivePage();
if (!activePage) {
setMessage('No Active page found');
return;
}
try {
// Change the visual type using powerbi-report-authoring
// For more information: https://docs.microsoft.com/en-us/javascript/api/overview/powerbi/report-authoring-overview
const visual = await activePage.getVisualByName('VisualContainer6');
const response = await visual.changeType('lineChart');
setDisplayMessageAndConsole(`The ${visual.type} was updated to lineChart.`);
return response;
}
catch (error) {
if (error === 'PowerBIEntityNotFound') {
console.log('No Visual found with that name');
} else {
console.log(error);
}
}
};
/**
* Set display message and log it in the console
*
* @returns void
*/
const setDisplayMessageAndConsole = (message: string): void => {
setMessage(message);
console.log(message);
}
const controlButtons =
isEmbedded ?
<>
<button onClick = { changeVisualType }>
Change visual type</button>
<button onClick = { hideFilterPane }>
Hide filter pane</button>
<button onClick = { setDataSelectedEvent }>
Set event</button>
<label className = "display-message">
{ displayMessage }
</label>
</>
:
<>
<label className = "display-message position">
{ displayMessage }
</label>
<button onClick = { embedReport } className = "embed-report">
Embed Report</button>
</>;
const header =
<div className = "header">Power BI Embedded React Component Demo</div>;
const reportComponent =
<PowerBIEmbed
embedConfig = { sampleReportConfig }
eventHandlers = { eventHandlersMap }
cssClassName = { reportClass }
getEmbeddedComponent = { (embedObject: Embed) => {
console.log(`Embedded object of type "${ embedObject.embedtype }" received`);
setReport(embedObject as Report);
} }
/>;
const footer =
<div className = "footer">
<p>This demo is powered by Power BI Embedded Analytics</p>
<label className = "separator-pipe">|</label>
<img title = "Power-BI" alt = "PowerBI_Icon" className = "footer-icon" src = "./assets/PowerBI_Icon.png" />
<p>Explore our<a href = "https://aka.ms/pbijs/" target = "_blank" rel = "noreferrer noopener">Playground</a></p>
<label className = "separator-pipe">|</label>
<img title = "GitHub" alt = "GitHub_Icon" className = "footer-icon" src = "./assets/GitHub_Icon.png" />
<p>Find the<a href = "https://github.com/microsoft/PowerBI-client-react" target = "_blank" rel = "noreferrer noopener">source code</a></p>
</div>;
return (
<div className = "container">
{ header }
<div className = "controls">
{ controlButtons }
{ isEmbedded ? reportComponent : null }
</div>
{ footer }
</div>
);
}
export default DemoApp;

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

До

Ширина:  |  Высота:  |  Размер: 7.1 KiB

После

Ширина:  |  Высота:  |  Размер: 7.1 KiB

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

До

Ширина:  |  Высота:  |  Размер: 2.0 KiB

После

Ширина:  |  Высота:  |  Размер: 2.0 KiB

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

@ -1,16 +1,16 @@
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="./public/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Wrapper demo</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="./public/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Wrapper demo</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>

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

@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import 'react-app-polyfill/ie11'; // For IE compatibility
import 'react-app-polyfill/stable'; // For IE compatibility
import React from 'react';
import ReactDOM from 'react-dom';
import DemoApp from './DemoApp';
ReactDOM.render(
<DemoApp/>,
document.getElementById('root')
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import 'react-app-polyfill/ie11'; // For IE compatibility
import 'react-app-polyfill/stable'; // For IE compatibility
import React from 'react';
import ReactDOM from 'react-dom';
import DemoApp from './DemoApp';
ReactDOM.render(
<DemoApp/>,
document.getElementById('root')
);

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

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

До

Ширина:  |  Высота:  |  Размер: 3.8 KiB

После

Ширина:  |  Высота:  |  Размер: 3.8 KiB

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

@ -1,18 +1,18 @@
{
"include": [
"./**/*.tsx",
"./**/*.ts"
],
"compilerOptions": {
"target": "es6",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noErrorTruncation": true,
"forceConsistentCasingInFileNames": true,
"module": "ES6",
"moduleResolution": "node",
"resolveJsonModule": true,
"jsx": "react",
}
{
"include": [
"./**/*.tsx",
"./**/*.ts"
],
"compilerOptions": {
"target": "es6",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noErrorTruncation": true,
"forceConsistentCasingInFileNames": true,
"module": "ES6",
"moduleResolution": "node",
"resolveJsonModule": true,
"jsx": "react",
}
}

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

@ -1,36 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const path = require('path');
module.exports = {
mode: 'development',
entry: path.resolve('index.tsx'),
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.ts(x)?$/,
loader: 'ts-loader'
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
]
},
resolve: {
extensions: [
'.tsx',
'.ts',
'.js',
]
},
devtool: 'source-map',
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const path = require('path');
module.exports = {
mode: 'development',
entry: path.resolve('src/index.tsx'),
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.ts(x)?$/,
loader: 'ts-loader'
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
]
},
resolve: {
extensions: [
'.tsx',
'.ts',
'.js',
]
},
devtool: 'source-map',
};

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

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

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

@ -1,21 +1,21 @@
{
"include": [
"../../src/**/*.tsx",
"../../src/**/*.ts"
],
"compilerOptions": {
"lib": ["ES2016"],
"target": "es5",
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"noErrorTruncation": true,
"module": "ES6",
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"noImplicitAny": true,
"declaration": true,
"outDir": "../../dist",
}
{
"include": [
"../../src/**/*.tsx",
"../../src/**/*.ts"
],
"compilerOptions": {
"lib": ["ES2016"],
"target": "es5",
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"noErrorTruncation": true,
"module": "ES6",
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"noImplicitAny": true,
"declaration": true,
"outDir": "../../dist",
}
}

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

@ -1,40 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
let path = require('path');
module.exports = {
entry: path.resolve('src/PowerBIEmbed.tsx'),
output: {
library: 'powerbi-client-react',
libraryTarget: 'umd',
path: path.resolve('dist'),
filename: 'powerbi-client-react.js'
},
externals: [
'react',
'powerbi-client',
'lodash.isequal'
],
module: {
rules: [
{
test: /\.ts(x)?$/,
loader: 'ts-loader',
options: {
configFile: path.resolve('config/src/tsconfig.json')
},
exclude: /node_modules/
},
]
},
resolve: {
modules: ['node_modules'],
extensions: [
'.tsx',
'.ts',
'.js'
]
},
devtool: 'source-map',
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const path = require('path');
module.exports = {
entry: path.resolve('src/PowerBIEmbed.tsx'),
output: {
library: 'powerbi-client-react',
libraryTarget: 'umd',
path: path.resolve('dist'),
filename: 'powerbi-client-react.js'
},
externals: [
'react',
'powerbi-client',
'lodash.isequal'
],
module: {
rules: [
{
test: /\.ts(x)?$/,
loader: 'ts-loader',
options: {
configFile: path.resolve('config/src/tsconfig.json')
},
exclude: /node_modules/
},
]
},
resolve: {
modules: ['node_modules'],
extensions: [
'.tsx',
'.ts',
'.js'
]
},
devtool: 'source-map',
};

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

@ -1,70 +1,70 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
let path = require('path');
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
path.resolve('compiledTests/**/*spec.js')
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
],
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ["Chrome_headless"],
customLaunchers: {
'Chrome_headless': {
base: 'Chrome',
flags: [
'--no-sandbox',
]
},
},
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
let path = require('path');
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
path.resolve('compiledTests/**/*spec.js')
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
],
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ["Chrome_headless"],
customLaunchers: {
'Chrome_headless': {
base: 'Chrome',
flags: [
'--no-sandbox',
]
},
},
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}

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

@ -1,20 +1,20 @@
{
"include": [
"../../test/**/*.tsx",
"../../test/**/*.ts"
],
"compilerOptions": {
"target": "ES5",
"lib": [
"ES2016",
"dom"
],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": false,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"jsx": "react"
}
{
"include": [
"../../test/**/*.tsx",
"../../test/**/*.ts"
],
"compilerOptions": {
"target": "ES5",
"lib": [
"ES2016",
"dom"
],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": false,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"jsx": "react"
}
}

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

@ -1,36 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
let path = require('path');
module.exports = {
mode: 'development',
entry: {
PowerBIEmbedTest: path.resolve('test/PowerBIEmbed.spec.tsx'),
utilsTest: path.resolve('test/utils.spec.ts'),
},
output: {
path: path.resolve('compiledTests'),
filename: '[name].spec.js'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.ts(x)?$/,
loader: 'ts-loader',
options: {
configFile: path.resolve('config/test/tsconfig.json')
},
exclude: /node_modules/
},
]
},
resolve: {
extensions: [
'.tsx',
'.ts',
'.js'
]
},
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const path = require('path');
module.exports = {
mode: 'development',
entry: {
PowerBIEmbedTest: path.resolve('test/PowerBIEmbed.spec.tsx'),
utilsTest: path.resolve('test/utils.spec.ts'),
},
output: {
path: path.resolve('compiledTests'),
filename: '[name].spec.js'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.ts(x)?$/,
loader: 'ts-loader',
options: {
configFile: path.resolve('config/test/tsconfig.json')
},
exclude: /node_modules/
},
]
},
resolve: {
extensions: [
'.tsx',
'.ts',
'.js'
]
},
};

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

@ -13,8 +13,7 @@
"build:dev": "webpack --mode=development --config config/src/webpack.config.js",
"pretest": "webpack --config config/test/webpack.config.js",
"test": "karma start config/test/karma.conf.js",
"install:demo": "cd demo && npm install",
"demo": "cd demo && npm run demo",
"demo": "cd ../demo && npm install && npm run demo",
"lint": "eslint --fix src/**/*.{ts,tsx}"
},
"keywords": [

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

@ -1,386 +1,386 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import * as React from 'react';
import {
service,
factories,
Report,
Embed,
Dashboard,
Tile,
Qna,
Visual,
IEmbedSettings,
IEmbedConfiguration,
IQnaEmbedConfiguration,
IVisualEmbedConfiguration,
IReportEmbedConfiguration,
IDashboardEmbedConfiguration,
ITileEmbedConfiguration,
} from 'powerbi-client';
import { ReportLevelFilters } from 'powerbi-models';
import isEqual from 'lodash.isequal';
import { stringifyMap, SdkType, SdkWrapperVersion } from './utils';
/**
* Type for event handler function of embedded entity
*/
export type EventHandler = {
(event?: service.ICustomEvent<any>, embeddedEntity?: Embed): void | null;
};
/**
* Props interface for PowerBIEmbed component
*/
export interface EmbedProps {
// Configuration for embedding the PowerBI entity (Required)
embedConfig:
| IReportEmbedConfiguration
| IDashboardEmbedConfiguration
| ITileEmbedConfiguration
| IQnaEmbedConfiguration
| IVisualEmbedConfiguration
| IEmbedConfiguration;
// Callback method to get the embedded PowerBI entity object (Optional)
getEmbeddedComponent?: { (embeddedComponent: Embed): void };
// Map of pair of event name and its handler method to be triggered on the event (Optional)
eventHandlers?: Map<string, EventHandler>;
// CSS class to be set on the embedding container (Optional)
cssClassName?: string;
// Phased embedding flag (Optional)
phasedEmbedding?: boolean;
// Provide a custom implementation of PowerBI service (Optional)
service?: service.Service;
}
export enum EmbedType {
Report = 'report',
Dashboard = 'dashboard',
Tile = 'tile',
Qna = 'qna',
Visual = 'visual'
}
/**
* Base react component to embed Power BI entities like: reports, dashboards, tiles, visual and qna containers.
*/
export class PowerBIEmbed extends React.Component<EmbedProps> {
// Embedded entity
// Note: Do not read or assign to this member variable directly, instead use the getter and setter
private _embed?: Embed;
// Powerbi service
private powerbi: service.Service;
// Ref to the HTML div element
private containerRef = React.createRef<HTMLDivElement>();
// JSON stringify of prev event handler map
private prevEventHandlerMapString = '';
// Getter for this._embed
private get embed(): Embed | undefined {
return this._embed;
};
// Setter for this._embed
private set embed(newEmbedInstance: Embed | undefined) {
this._embed = newEmbedInstance;
// Invoke callback method in props to return this embed instance
this.invokeGetEmbedCallback();
};
constructor(props: EmbedProps) {
super(props);
if (this.props.service) {
this.powerbi = this.props.service;
}
else {
this.powerbi = new service.Service(
factories.hpmFactory,
factories.wpmpFactory,
factories.routerFactory);
}
this.powerbi.setSdkInfo(SdkType, SdkWrapperVersion);
};
componentDidMount(): void {
// Check if HTML container is available
if (this.containerRef.current) {
// Decide to embed, load or bootstrap
if (this.props.embedConfig.accessToken && this.props.embedConfig.embedUrl) {
this.embedEntity();
}
else {
this.embed = this.powerbi.bootstrap(this.containerRef.current, this.props.embedConfig);
}
}
// Set event handlers if available
if (this.props.eventHandlers && this.embed) {
this.setEventHandlers(this.embed, this.props.eventHandlers);
}
};
async componentDidUpdate(prevProps: EmbedProps): Promise<void> {
this.embedOrUpdateAccessToken(prevProps);
// Set event handlers if available
if (this.props.eventHandlers && this.embed) {
this.setEventHandlers(this.embed, this.props.eventHandlers);
}
// Allow settings update only when settings object in embedConfig of current and previous props is different
if (!isEqual(this.props.embedConfig.settings, prevProps.embedConfig.settings)) {
await this.updateSettings();
}
// Update pageName and filters for a report
if (this.props.embedConfig.type === EmbedType.Report) {
try {
// Typecasting to IReportEmbedConfiguration
const embedConfig = this.props.embedConfig as IReportEmbedConfiguration;
const filters = embedConfig.filters as ReportLevelFilters[];
const prevEmbedConfig = prevProps.embedConfig as IReportEmbedConfiguration;
// Set new page if available and different from the previous page
if (embedConfig.pageName && embedConfig.pageName !== prevEmbedConfig.pageName) {
// Upcast to Report and call setPage
await (this.embed as Report).setPage(embedConfig.pageName);
}
// Set filters on the embedded report if available and different from the previous filter
if (filters && !isEqual(filters, prevEmbedConfig.filters)) {
// Upcast to Report and call setFilters
await (this.embed as Report).setFilters(filters);
}
// Remove filters on the embedded report, if previously applied
else if (!filters && prevEmbedConfig.filters) {
// Upcast to Report and call removeFilters
await (this.embed as Report).removeFilters();
}
} catch (err) {
console.error(err);
}
}
};
componentWillUnmount(): void {
// Clean Up
if (this.containerRef.current) {
this.powerbi.reset(this.containerRef.current);
}
};
render(): JSX.Element {
return (
<div
ref={this.containerRef}
className={this.props.cssClassName}>
</div>
)
};
/**
* Embed the powerbi entity (Load for phased embedding)
*/
private embedEntity(): void {
// Check if the HTML container is rendered and available
if (!this.containerRef.current) {
return;
}
// Load when props.phasedEmbedding is true and embed type is report, embed otherwise
if (this.props.phasedEmbedding && this.props.embedConfig.type === EmbedType.Report) {
this.embed = this.powerbi.load(this.containerRef.current, this.props.embedConfig);
}
else {
if (this.props.phasedEmbedding) {
console.error(`Phased embedding is not supported for type ${this.props.embedConfig.type}`)
}
this.embed = this.powerbi.embed(this.containerRef.current, this.props.embedConfig);
}
}
/**
* When component updates, choose to _embed_ the powerbi entity or _update the accessToken_ in the embedded entity
* or do nothing if the embedUrl and accessToken did not update in the new props
*
* @param prevProps EmbedProps
* @returns void
*/
private embedOrUpdateAccessToken(prevProps: EmbedProps): void {
// Check if Embed URL and Access Token are present in current props
if (!this.props.embedConfig.accessToken || !this.props.embedConfig.embedUrl) {
return;
}
// Embed or load in the following scenarios
// 1. AccessToken was not provided in prev props (E.g. Report was bootstrapped earlier)
// 2. Embed URL is updated (E.g. New report is to be embedded)
if (
this.containerRef.current &&
(!prevProps.embedConfig.accessToken ||
this.props.embedConfig.embedUrl !== prevProps.embedConfig.embedUrl)
) {
this.embedEntity();
}
// Set new access token,
// when access token is updated but embed Url is same
else if (
this.props.embedConfig.accessToken !== prevProps.embedConfig.accessToken &&
this.props.embedConfig.embedUrl === prevProps.embedConfig.embedUrl &&
this.embed
) {
this.embed.setAccessToken(this.props.embedConfig.accessToken)
.catch((error) => {
console.error(`setAccessToken error: ${error}`);
});
}
}
/**
* Sets all event handlers from the props on the embedded entity
*
* @param embed Embedded object
* @param eventHandlers Array of eventhandlers to be set on embedded entity
* @returns void
*/
private setEventHandlers(
embed: Embed,
eventHandlerMap: Map<string, EventHandler>
): void {
// Get string representation of eventHandlerMap
const eventHandlerMapString = stringifyMap(this.props.eventHandlers);
// Check if event handler map changed
if (this.prevEventHandlerMapString === eventHandlerMapString) {
return;
}
// Update prev string representation of event handler map
this.prevEventHandlerMapString = eventHandlerMapString;
// List of allowed events
let allowedEvents = Embed.allowedEvents;
const entityType = embed.embedtype;
// Append entity specific events
switch (entityType) {
case EmbedType.Report:
allowedEvents = [...allowedEvents, ...Report.allowedEvents]
break;
case EmbedType.Dashboard:
allowedEvents = [...allowedEvents, ...Dashboard.allowedEvents]
break;
case EmbedType.Tile:
allowedEvents = [...allowedEvents, ...Tile.allowedEvents]
break;
case EmbedType.Qna:
allowedEvents = [...allowedEvents, ...Qna.allowedEvents]
break;
case EmbedType.Visual:
allowedEvents = [...allowedEvents, ...Visual.allowedEvents]
break;
default:
console.error(`Invalid embed type ${entityType}`);
}
// Holds list of events which are not allowed
const invalidEvents: Array<string> = [];
// Apply all provided event handlers
eventHandlerMap.forEach((eventHandlerMethod, eventName) => {
// Check if this event is allowed
if (allowedEvents.includes(eventName)) {
// Removes event handler for this event
embed.off(eventName);
// Event handler is effectively removed for this event when eventHandlerMethod is null
if (eventHandlerMethod) {
// Set single event handler
embed.on(eventName, (event: service.ICustomEvent<any>): void => {
eventHandlerMethod(event, this.embed);
});
}
}
else {
// Add this event name to the list of invalid events
invalidEvents.push(eventName);
}
});
// Handle invalid events
if (invalidEvents.length) {
console.error(`Following events are invalid: ${invalidEvents.join(',')}`);
}
};
/**
* Returns the embedded object via _getEmbed_ callback method provided in props
*
* @returns void
*/
private invokeGetEmbedCallback(): void {
if (this.props.getEmbeddedComponent && this.embed) {
this.props.getEmbeddedComponent(this.embed);
}
};
/**
* Update settings from props of the embedded artifact
*
* @returns void
*/
private async updateSettings(): Promise<void> {
if (!this.embed || !this.props.embedConfig.settings) {
return;
}
switch (this.props.embedConfig.type) {
case EmbedType.Report: {
// Typecasted to IEmbedSettings as props.embedConfig.settings can be ISettings via IQnaEmbedConfiguration
const settings = this.props.embedConfig.settings as IEmbedSettings;
try {
// Upcast to Report and call updateSettings
await (this.embed as Report).updateSettings(settings);
} catch (error) {
console.error(`Error in method updateSettings: ${error}`);
}
break;
}
case EmbedType.Dashboard:
case EmbedType.Tile:
case EmbedType.Qna:
case EmbedType.Visual:
// updateSettings not applicable for these embedding types
break;
default:
console.error(`Invalid embed type ${this.props.embedConfig.type}`);
}
};
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import * as React from 'react';
import {
service,
factories,
Report,
Embed,
Dashboard,
Tile,
Qna,
Visual,
IEmbedSettings,
IEmbedConfiguration,
IQnaEmbedConfiguration,
IVisualEmbedConfiguration,
IReportEmbedConfiguration,
IDashboardEmbedConfiguration,
ITileEmbedConfiguration,
} from 'powerbi-client';
import { ReportLevelFilters } from 'powerbi-models';
import isEqual from 'lodash.isequal';
import { stringifyMap, SdkType, SdkWrapperVersion } from './utils';
/**
* Type for event handler function of embedded entity
*/
export type EventHandler = {
(event?: service.ICustomEvent<any>, embeddedEntity?: Embed): void | null;
};
/**
* Props interface for PowerBIEmbed component
*/
export interface EmbedProps {
// Configuration for embedding the PowerBI entity (Required)
embedConfig:
| IReportEmbedConfiguration
| IDashboardEmbedConfiguration
| ITileEmbedConfiguration
| IQnaEmbedConfiguration
| IVisualEmbedConfiguration
| IEmbedConfiguration;
// Callback method to get the embedded PowerBI entity object (Optional)
getEmbeddedComponent?: { (embeddedComponent: Embed): void };
// Map of pair of event name and its handler method to be triggered on the event (Optional)
eventHandlers?: Map<string, EventHandler>;
// CSS class to be set on the embedding container (Optional)
cssClassName?: string;
// Phased embedding flag (Optional)
phasedEmbedding?: boolean;
// Provide a custom implementation of PowerBI service (Optional)
service?: service.Service;
}
export enum EmbedType {
Report = 'report',
Dashboard = 'dashboard',
Tile = 'tile',
Qna = 'qna',
Visual = 'visual'
}
/**
* Base react component to embed Power BI entities like: reports, dashboards, tiles, visual and qna containers.
*/
export class PowerBIEmbed extends React.Component<EmbedProps> {
// Embedded entity
// Note: Do not read or assign to this member variable directly, instead use the getter and setter
private _embed?: Embed;
// Powerbi service
private powerbi: service.Service;
// Ref to the HTML div element
private containerRef = React.createRef<HTMLDivElement>();
// JSON stringify of prev event handler map
private prevEventHandlerMapString = '';
// Getter for this._embed
private get embed(): Embed | undefined {
return this._embed;
};
// Setter for this._embed
private set embed(newEmbedInstance: Embed | undefined) {
this._embed = newEmbedInstance;
// Invoke callback method in props to return this embed instance
this.invokeGetEmbedCallback();
};
constructor(props: EmbedProps) {
super(props);
if (this.props.service) {
this.powerbi = this.props.service;
}
else {
this.powerbi = new service.Service(
factories.hpmFactory,
factories.wpmpFactory,
factories.routerFactory);
}
this.powerbi.setSdkInfo(SdkType, SdkWrapperVersion);
};
componentDidMount(): void {
// Check if HTML container is available
if (this.containerRef.current) {
// Decide to embed, load or bootstrap
if (this.props.embedConfig.accessToken && this.props.embedConfig.embedUrl) {
this.embedEntity();
}
else {
this.embed = this.powerbi.bootstrap(this.containerRef.current, this.props.embedConfig);
}
}
// Set event handlers if available
if (this.props.eventHandlers && this.embed) {
this.setEventHandlers(this.embed, this.props.eventHandlers);
}
};
async componentDidUpdate(prevProps: EmbedProps): Promise<void> {
this.embedOrUpdateAccessToken(prevProps);
// Set event handlers if available
if (this.props.eventHandlers && this.embed) {
this.setEventHandlers(this.embed, this.props.eventHandlers);
}
// Allow settings update only when settings object in embedConfig of current and previous props is different
if (!isEqual(this.props.embedConfig.settings, prevProps.embedConfig.settings)) {
await this.updateSettings();
}
// Update pageName and filters for a report
if (this.props.embedConfig.type === EmbedType.Report) {
try {
// Typecasting to IReportEmbedConfiguration
const embedConfig = this.props.embedConfig as IReportEmbedConfiguration;
const filters = embedConfig.filters as ReportLevelFilters[];
const prevEmbedConfig = prevProps.embedConfig as IReportEmbedConfiguration;
// Set new page if available and different from the previous page
if (embedConfig.pageName && embedConfig.pageName !== prevEmbedConfig.pageName) {
// Upcast to Report and call setPage
await (this.embed as Report).setPage(embedConfig.pageName);
}
// Set filters on the embedded report if available and different from the previous filter
if (filters && !isEqual(filters, prevEmbedConfig.filters)) {
// Upcast to Report and call setFilters
await (this.embed as Report).setFilters(filters);
}
// Remove filters on the embedded report, if previously applied
else if (!filters && prevEmbedConfig.filters) {
// Upcast to Report and call removeFilters
await (this.embed as Report).removeFilters();
}
} catch (err) {
console.error(err);
}
}
};
componentWillUnmount(): void {
// Clean Up
if (this.containerRef.current) {
this.powerbi.reset(this.containerRef.current);
}
};
render(): JSX.Element {
return (
<div
ref={this.containerRef}
className={this.props.cssClassName}>
</div>
)
};
/**
* Embed the powerbi entity (Load for phased embedding)
*/
private embedEntity(): void {
// Check if the HTML container is rendered and available
if (!this.containerRef.current) {
return;
}
// Load when props.phasedEmbedding is true and embed type is report, embed otherwise
if (this.props.phasedEmbedding && this.props.embedConfig.type === EmbedType.Report) {
this.embed = this.powerbi.load(this.containerRef.current, this.props.embedConfig);
}
else {
if (this.props.phasedEmbedding) {
console.error(`Phased embedding is not supported for type ${this.props.embedConfig.type}`)
}
this.embed = this.powerbi.embed(this.containerRef.current, this.props.embedConfig);
}
}
/**
* When component updates, choose to _embed_ the powerbi entity or _update the accessToken_ in the embedded entity
* or do nothing if the embedUrl and accessToken did not update in the new props
*
* @param prevProps EmbedProps
* @returns void
*/
private embedOrUpdateAccessToken(prevProps: EmbedProps): void {
// Check if Embed URL and Access Token are present in current props
if (!this.props.embedConfig.accessToken || !this.props.embedConfig.embedUrl) {
return;
}
// Embed or load in the following scenarios
// 1. AccessToken was not provided in prev props (E.g. Report was bootstrapped earlier)
// 2. Embed URL is updated (E.g. New report is to be embedded)
if (
this.containerRef.current &&
(!prevProps.embedConfig.accessToken ||
this.props.embedConfig.embedUrl !== prevProps.embedConfig.embedUrl)
) {
this.embedEntity();
}
// Set new access token,
// when access token is updated but embed Url is same
else if (
this.props.embedConfig.accessToken !== prevProps.embedConfig.accessToken &&
this.props.embedConfig.embedUrl === prevProps.embedConfig.embedUrl &&
this.embed
) {
this.embed.setAccessToken(this.props.embedConfig.accessToken)
.catch((error) => {
console.error(`setAccessToken error: ${error}`);
});
}
}
/**
* Sets all event handlers from the props on the embedded entity
*
* @param embed Embedded object
* @param eventHandlers Array of eventhandlers to be set on embedded entity
* @returns void
*/
private setEventHandlers(
embed: Embed,
eventHandlerMap: Map<string, EventHandler>
): void {
// Get string representation of eventHandlerMap
const eventHandlerMapString = stringifyMap(this.props.eventHandlers);
// Check if event handler map changed
if (this.prevEventHandlerMapString === eventHandlerMapString) {
return;
}
// Update prev string representation of event handler map
this.prevEventHandlerMapString = eventHandlerMapString;
// List of allowed events
let allowedEvents = Embed.allowedEvents;
const entityType = embed.embedtype;
// Append entity specific events
switch (entityType) {
case EmbedType.Report:
allowedEvents = [...allowedEvents, ...Report.allowedEvents]
break;
case EmbedType.Dashboard:
allowedEvents = [...allowedEvents, ...Dashboard.allowedEvents]
break;
case EmbedType.Tile:
allowedEvents = [...allowedEvents, ...Tile.allowedEvents]
break;
case EmbedType.Qna:
allowedEvents = [...allowedEvents, ...Qna.allowedEvents]
break;
case EmbedType.Visual:
allowedEvents = [...allowedEvents, ...Visual.allowedEvents]
break;
default:
console.error(`Invalid embed type ${entityType}`);
}
// Holds list of events which are not allowed
const invalidEvents: Array<string> = [];
// Apply all provided event handlers
eventHandlerMap.forEach((eventHandlerMethod, eventName) => {
// Check if this event is allowed
if (allowedEvents.includes(eventName)) {
// Removes event handler for this event
embed.off(eventName);
// Event handler is effectively removed for this event when eventHandlerMethod is null
if (eventHandlerMethod) {
// Set single event handler
embed.on(eventName, (event: service.ICustomEvent<any>): void => {
eventHandlerMethod(event, this.embed);
});
}
}
else {
// Add this event name to the list of invalid events
invalidEvents.push(eventName);
}
});
// Handle invalid events
if (invalidEvents.length) {
console.error(`Following events are invalid: ${invalidEvents.join(',')}`);
}
};
/**
* Returns the embedded object via _getEmbed_ callback method provided in props
*
* @returns void
*/
private invokeGetEmbedCallback(): void {
if (this.props.getEmbeddedComponent && this.embed) {
this.props.getEmbeddedComponent(this.embed);
}
};
/**
* Update settings from props of the embedded artifact
*
* @returns void
*/
private async updateSettings(): Promise<void> {
if (!this.embed || !this.props.embedConfig.settings) {
return;
}
switch (this.props.embedConfig.type) {
case EmbedType.Report: {
// Typecasted to IEmbedSettings as props.embedConfig.settings can be ISettings via IQnaEmbedConfiguration
const settings = this.props.embedConfig.settings as IEmbedSettings;
try {
// Upcast to Report and call updateSettings
await (this.embed as Report).updateSettings(settings);
} catch (error) {
console.error(`Error in method updateSettings: ${error}`);
}
break;
}
case EmbedType.Dashboard:
case EmbedType.Tile:
case EmbedType.Qna:
case EmbedType.Visual:
// updateSettings not applicable for these embedding types
break;
default:
console.error(`Invalid embed type ${this.props.embedConfig.type}`);
}
};
}

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

@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export {
PowerBIEmbed,
EmbedProps,
EmbedType,
EventHandler
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export {
PowerBIEmbed,
EmbedProps,
EmbedType,
EventHandler
} from './PowerBIEmbed'

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

@ -1,47 +1,47 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { EmbedProps } from "./PowerBIEmbed";
/**
* Get JSON string representation of the given map.
*
* @param map Map of event and corresponding handler method
*
* For example:
* Input:
* ```
* Map([
['loaded', null],
['rendered', function () { console.log('Rendered'); }]
]);
* ```
* Output:
* ```
* `[["loaded",""],["rendered","function () { console.log('Rendered'); }"]]`
* ```
*/
export function stringifyMap(map: EmbedProps['eventHandlers']): string {
// Return empty string for empty/null map
if (!map) {
return '';
}
// Get entries of map as array
const mapEntries = Array.from(map);
// Return JSON string
return JSON.stringify(mapEntries.map((mapEntry) => {
// Convert event handler method to a string containing its source code for comparison
return [
mapEntry[0],
mapEntry[1] ? mapEntry[1].toString() : ''
];
}));
};
// SDK information to be used with service instance
export const SdkType = "powerbi-client-react";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { EmbedProps } from "./PowerBIEmbed";
/**
* Get JSON string representation of the given map.
*
* @param map Map of event and corresponding handler method
*
* For example:
* Input:
* ```
* Map([
['loaded', null],
['rendered', function () { console.log('Rendered'); }]
]);
* ```
* Output:
* ```
* `[["loaded",""],["rendered","function () { console.log('Rendered'); }"]]`
* ```
*/
export function stringifyMap(map: EmbedProps['eventHandlers']): string {
// Return empty string for empty/null map
if (!map) {
return '';
}
// Get entries of map as array
const mapEntries = Array.from(map);
// Return JSON string
return JSON.stringify(mapEntries.map((mapEntry) => {
// Convert event handler method to a string containing its source code for comparison
return [
mapEntry[0],
mapEntry[1] ? mapEntry[1].toString() : ''
];
}));
};
// SDK information to be used with service instance
export const SdkType = "powerbi-client-react";
export const SdkWrapperVersion = "1.3.5";

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

@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const mockedMethods = ['init', 'embed', 'bootstrap', 'load', 'get', 'reset', 'preload', 'setSdkInfo'];
const mockPowerBIService = jasmine.createSpyObj('mockService', mockedMethods);
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const mockedMethods = ['init', 'embed', 'bootstrap', 'load', 'get', 'reset', 'preload', 'setSdkInfo'];
const mockPowerBIService = jasmine.createSpyObj('mockService', mockedMethods);
export { mockPowerBIService, mockedMethods };

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

@ -1,71 +1,71 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { service } from 'powerbi-client';
import { stringifyMap } from '../src/utils';
describe('tests of PowerBIEmbed', function () {
let container: HTMLDivElement | null;
beforeEach(function () {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(function () {
if (container){
document.body.removeChild(container);
container = null;
}
});
// Tests for utils stringifyMap
describe('tests PowerBIEmbed stringifyMap method', () => {
it('stringifies the event handler map', () => {
// Arrange
const eventHandlerMap = new Map([
['loaded', function () { console.log('Report loaded'); }],
['rendered', function () { console.log('Rendered'); }]
]);
const expectedString = `[["loaded","function () { console.log('Report loaded'); }"],["rendered","function () { console.log('Rendered'); }"]]`;
// Act
const jsonStringOutput = stringifyMap(eventHandlerMap);
// Assert
expect(jsonStringOutput).toBe(expectedString);
});
it('stringifies empty event handler map', () => {
// Arrange
const eventHandlerMap = new Map<string, service.IEventHandler<any>>([]);
const expectedString = `[]`;
// Act
const jsonStringOutput = stringifyMap(eventHandlerMap);
// Assert
expect(jsonStringOutput).toBe(expectedString);
});
it('stringifies null in event handler map', () => {
// Arrange
const eventHandlerMap = new Map([
['loaded', null],
['rendered', function () { console.log('Rendered'); }]
]);
const expectedString = `[["loaded",""],["rendered","function () { console.log('Rendered'); }"]]`;
// Act
const jsonStringOutput = stringifyMap(eventHandlerMap);
// Assert
expect(jsonStringOutput).toBe(expectedString);
});
});
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { service } from 'powerbi-client';
import { stringifyMap } from '../src/utils';
describe('tests of PowerBIEmbed', function () {
let container: HTMLDivElement | null;
beforeEach(function () {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(function () {
if (container){
document.body.removeChild(container);
container = null;
}
});
// Tests for utils stringifyMap
describe('tests PowerBIEmbed stringifyMap method', () => {
it('stringifies the event handler map', () => {
// Arrange
const eventHandlerMap = new Map([
['loaded', function () { console.log('Report loaded'); }],
['rendered', function () { console.log('Rendered'); }]
]);
const expectedString = `[["loaded","function () { console.log('Report loaded'); }"],["rendered","function () { console.log('Rendered'); }"]]`;
// Act
const jsonStringOutput = stringifyMap(eventHandlerMap);
// Assert
expect(jsonStringOutput).toBe(expectedString);
});
it('stringifies empty event handler map', () => {
// Arrange
const eventHandlerMap = new Map<string, service.IEventHandler<any>>([]);
const expectedString = `[]`;
// Act
const jsonStringOutput = stringifyMap(eventHandlerMap);
// Assert
expect(jsonStringOutput).toBe(expectedString);
});
it('stringifies null in event handler map', () => {
// Arrange
const eventHandlerMap = new Map([
['loaded', null],
['rendered', function () { console.log('Rendered'); }]
]);
const expectedString = `[["loaded",""],["rendered","function () { console.log('Rendered'); }"]]`;
// Act
const jsonStringOutput = stringifyMap(eventHandlerMap);
// Assert
expect(jsonStringOutput).toBe(expectedString);
});
});
});

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

@ -14,7 +14,7 @@ Instead, please report them to the Microsoft Security Response Center (MSRC) at
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:

1
owners.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
corembed