Merge pull request #384 from microsoft/release-2.22.2

Release 2.22.2
This commit is contained in:
Or Shemesh 2023-01-31 13:22:45 +02:00 коммит произвёл GitHub
Родитель e3e3282627 addc9bc2a7
Коммит c4578b614e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 2595 добавлений и 2489 удалений

184
dist/powerbi-client.d.ts поставляемый
Просмотреть файл

@ -1,4 +1,4 @@
// powerbi-client v2.21.1
// powerbi-client v2.22.2
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
declare module "config" {
@ -116,6 +116,14 @@ declare module "util" {
* @returns {number}
*/
export function getTimeDiffInMilliseconds(start: Date, end: Date): number;
/**
* Checks if the embed type is for create
*
* @export
* @param {string} embedType
* @returns {boolean}
*/
export function isCreate(embedType: string): boolean;
}
declare module "embed" {
import * as models from 'powerbi-models';
@ -142,6 +150,7 @@ declare module "embed" {
export type IDashboardEmbedConfiguration = models.IDashboardEmbedConfiguration;
export type ITileEmbedConfiguration = models.ITileEmbedConfiguration;
export type IQnaEmbedConfiguration = models.IQnaEmbedConfiguration;
export type IQuickCreateConfiguration = models.IQuickCreateConfiguration;
export type ILocaleSettings = models.ILocaleSettings;
export type IQnaSettings = models.IQnaSettings;
export type IEmbedSettings = models.ISettings;
@ -249,13 +258,6 @@ declare module "embed" {
* @hidden
*/
bootstrapConfig: IBootstrapEmbedConfiguration;
/**
* Gets or sets the configuration settings for creating report.
*
* @type {models.IReportCreateConfiguration}
* @hidden
*/
createConfig: models.IReportCreateConfiguration;
/**
* Url used in the load request.
*
@ -299,19 +301,12 @@ declare module "embed" {
*/
constructor(service: Service, element: HTMLElement, config: IEmbedConfigurationBase, iframe?: HTMLIFrameElement, phasedRender?: boolean, isBootstrap?: boolean);
/**
* Sends createReport configuration data.
*
* ```javascript
* createReport({
* datasetId: '5dac7a4a-4452-46b3-99f6-a25915e0fe55',
* accessToken: 'eyJ0eXA ... TaE2rTSbmg',
* ```
* Create is not supported by default
*
* @hidden
* @param {models.IReportCreateConfiguration} config
* @returns {Promise<void>}
*/
createReport(config: models.IReportCreateConfiguration): Promise<void>;
create(): Promise<void>;
/**
* Saves Report.
*
@ -1619,6 +1614,35 @@ declare module "report" {
* @param zoomLevel zoom level to set
*/
setZoom(zoomLevel: number): Promise<void>;
/**
* Closes all open context menus and tooltips.
*
* ```javascript
* report.closeAllOverlays()
* .then(() => {
* ...
* });
* ```
*
* @returns {Promise<void>}
*/
closeAllOverlays(): Promise<void>;
/**
* Clears selected not popped out visuals, if flag is passed, all visuals selections will be cleared.
*
* ```javascript
* report.clearSelectedVisuals()
* .then(() => {
* ...
* });
* ```
*
* @param {Boolean} [clearPopOutState=false]
* If false / undefined visuals selection will not be cleared if one of visuals
* is in popped out state (in focus, show as table, spotlight...)
* @returns {Promise<void>}
*/
clearSelectedVisuals(clearPopOutState?: boolean): Promise<void>;
}
}
declare module "create" {
@ -1633,6 +1657,13 @@ declare module "create" {
* @extends {Embed}
*/
export class Create extends Embed {
/**
* Gets or sets the configuration settings for creating report.
*
* @type {IReportCreateConfiguration}
* @hidden
*/
createConfig: IReportCreateConfiguration;
constructor(service: Service, element: HTMLElement, config: IEmbedConfiguration | IReportCreateConfiguration, phasedRender?: boolean, isBootstrap?: boolean);
/**
* Gets the dataset ID from the first available location: createConfig or embed url.
@ -1678,6 +1709,19 @@ declare module "create" {
* @hidden
*/
static findIdFromEmbedUrl(url: string): string;
/**
* Sends create configuration data.
*
* ```javascript
* create ({
* datasetId: '5dac7a4a-4452-46b3-99f6-a25915e0fe55',
* accessToken: 'eyJ0eXA ... TaE2rTSbmg',
* ```
*
* @hidden
* @returns {Promise<void>}
*/
create(): Promise<void>;
}
}
declare module "dashboard" {
@ -2003,11 +2047,70 @@ declare module "visual" {
private getFiltersLevelUrl;
}
}
declare module "quickCreate" {
import { IError, IQuickCreateConfiguration } from 'powerbi-models';
import { Service } from "service";
import { Embed, IEmbedConfigurationBase } from "embed";
/**
* A Power BI Quick Create component
*
* @export
* @class QuickCreate
* @extends {Embed}
*/
export class QuickCreate extends Embed {
/**
* Gets or sets the configuration settings for creating report.
*
* @type {IQuickCreateConfiguration}
* @hidden
*/
createConfig: IQuickCreateConfiguration;
constructor(service: Service, element: HTMLElement, config: IQuickCreateConfiguration, phasedRender?: boolean, isBootstrap?: boolean);
/**
* Override the getId abstract function
* QuickCreate does not need any ID
*
* @returns {string}
*/
getId(): string;
/**
* Validate create report configuration.
*/
validate(config: IEmbedConfigurationBase): IError[];
/**
* Handle config changes.
*
* @hidden
* @returns {void}
*/
configChanged(isBootstrap: boolean): void;
/**
* @hidden
* @returns {string}
*/
getDefaultEmbedUrlEndpoint(): string;
/**
* Sends quickCreate configuration data.
*
* ```javascript
* quickCreate({
* accessToken: 'eyJ0eXA ... TaE2rTSbmg',
* datasetCreateConfig: {}})
* ```
*
* @hidden
* @param {IQuickCreateConfiguration} createConfig
* @returns {Promise<void>}
*/
create(): Promise<void>;
}
}
declare module "service" {
import { WindowPostMessageProxy } from 'window-post-message-proxy';
import { HttpPostMessage } from 'http-post-message';
import { Router, IExtendedRequest, Response as IExtendedResponse } from 'powerbi-router';
import { IReportCreateConfiguration } from 'powerbi-models';
import { IQuickCreateConfiguration, IReportCreateConfiguration } from 'powerbi-models';
import { Embed, IBootstrapEmbedConfiguration, IDashboardEmbedConfiguration, IEmbedConfiguration, IEmbedConfigurationBase, IQnaEmbedConfiguration, IReportEmbedConfiguration, ITileEmbedConfiguration, IVisualEmbedConfiguration } from "embed";
export interface IEvent<T> {
type: string;
@ -2063,6 +2166,10 @@ declare module "service" {
hpm: HttpPostMessage;
}
export type IComponentEmbedConfiguration = IReportEmbedConfiguration | IDashboardEmbedConfiguration | ITileEmbedConfiguration | IVisualEmbedConfiguration | IQnaEmbedConfiguration;
/**
* @hidden
*/
export type EmbedComponentFactory = (service: Service, element: HTMLElement, config: IEmbedConfigurationBase, phasedRender?: boolean, isBootstrap?: boolean) => Embed;
/**
* The Power BI Service embed component, which is the entry point to embed all other Power BI components into your application
*
@ -2088,7 +2195,7 @@ declare module "service" {
accessToken: string;
/** The Configuration object for the service*/
private config;
/** A list of Dashboard, Report and Tile components that have been embedded using this service instance. */
/** A list of Power BI components that have been embedded using this service instance. */
private embeds;
/** TODO: Look for way to make hpm private without sacrificing ease of maintenance. This should be private but in embed needs to call methods.
*
@ -2102,6 +2209,10 @@ declare module "service" {
wpmp: WindowPostMessageProxy;
router: Router;
private uniqueSessionId;
/**
* @hidden
*/
private registeredComponents;
/**
* Creates an instance of a Power BI Service.
*
@ -2120,6 +2231,14 @@ declare module "service" {
* @returns {Embed}
*/
createReport(element: HTMLElement, config: IEmbedConfiguration | IReportCreateConfiguration): Embed;
/**
* Creates new dataset
*
* @param {HTMLElement} element
* @param {IEmbedConfiguration} [config={}]
* @returns {Embed}
*/
quickCreate(element: HTMLElement, config: IQuickCreateConfiguration): Embed;
/**
* TODO: Add a description here
*
@ -2175,10 +2294,25 @@ declare module "service" {
* @private
* @param {IPowerBiElement} element
* @param {IEmbedConfigurationBase} config
* @param {boolean} phasedRender
* @param {boolean} isBootstrap
* @returns {Embed}
* @hidden
*/
private embedNew;
/**
* Given component type, creates embed component instance
*
* @private
* @param {string} componentType
* @param {HTMLElement} element
* @param {IEmbedConfigurationBase} config
* @param {boolean} phasedRender
* @param {boolean} isBootstrap
* @returns {Embed}
* @hidden
*/
private createEmbedComponent;
/**
* Given an element that already contains an embed component, load with a new configuration.
*
@ -2263,6 +2397,15 @@ declare module "service" {
* @returns {void}
*/
setSdkInfo(type: string, version: string): void;
/**
* API for registering external components
*
* @hidden
* @param {string} componentType
* @param {EmbedComponentFactory} embedComponentFactory
* @param {string[]} routerEventUrls
*/
register(componentType: string, embedComponentFactory: EmbedComponentFactory, routerEventUrls: string[]): void;
}
}
declare module "bookmarksManager" {
@ -2797,11 +2940,12 @@ declare module "powerbi-client" {
export { Report } from "report";
export { Dashboard } from "dashboard";
export { Tile } from "tile";
export { IEmbedConfiguration, IQnaEmbedConfiguration, IVisualEmbedConfiguration, IReportEmbedConfiguration, IDashboardEmbedConfiguration, ITileEmbedConfiguration, Embed, ILocaleSettings, IEmbedSettings, IQnaSettings, } from "embed";
export { IEmbedConfiguration, IQnaEmbedConfiguration, IVisualEmbedConfiguration, IReportEmbedConfiguration, IDashboardEmbedConfiguration, ITileEmbedConfiguration, IQuickCreateConfiguration, Embed, ILocaleSettings, IEmbedSettings, IQnaSettings, } from "embed";
export { Page } from "page";
export { Qna } from "qna";
export { Visual } from "visual";
export { VisualDescriptor } from "visualDescriptor";
export { QuickCreate } from "quickCreate";
export { BasicFilterBuilder, AdvancedFilterBuilder, TopNFilterBuilder, RelativeDateFilterBuilder, RelativeTimeFilterBuilder } from "FilterBuilders/index";
global {
interface Window {

4101
dist/powerbi.js поставляемый

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

22
dist/powerbi.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@ -161,7 +161,7 @@ gulp.task('min:js', 'Creates minified JavaScript file', function () {
// Create minified bundle without source map
webpackConfig.mode = 'production';
webpackConfig.devtool = 'none';
webpackConfig.devtool = false;
return gulp.src(['./src/powerbi-client.ts'])
.pipe(webpackStream({

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

@ -1,6 +1,6 @@
var argv = require('yargs').argv;
var browserName = 'PhantomJS';
var browserName = 'Chrome_headless';
if (argv.chrome) {
browserName = 'Chrome_headless'
}
@ -32,7 +32,6 @@ module.exports = function (config) {
'karma-chrome-launcher',
'karma-jasmine',
'karma-spec-reporter',
'karma-phantomjs-launcher',
'karma-jasmine-html-reporter',
'karma-junit-reporter'
],

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

@ -1,6 +1,6 @@
{
"name": "powerbi-client",
"version": "2.21.1",
"version": "2.22.2",
"description": "JavaScript library for embedding Power BI into your apps. Provides service which makes it easy to embed different types of components and an object model which allows easy interaction with these components such as changing pages, applying filters, and responding to data selection.",
"main": "dist/powerbi.js",
"types": "dist/powerbi-client.d.ts",
@ -47,17 +47,17 @@
"eslint-plugin-prefer-arrow": "^1.2.2",
"gulp": "^4.0.2",
"gulp-eslint": "^6.0.0",
"gulp-flatten": "^0.2.0",
"gulp-flatten": "^0.4.0",
"gulp-gh-pages": "^0.5.4",
"gulp-header": "^1.8.7",
"gulp-header": "^2.0.9",
"gulp-help-four": "^0.2.3",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.5.4",
"gulp-typedoc": "^2.0.0",
"gulp-typescript": "^4.0.1",
"gulp-typescript": "^6.0.0-alpha.1",
"gulp-watch": "^5.0.1",
"gulp4-run-sequence": "^1.0.0",
"http-server": "^0.12.1",
"http-server": "^14.1.1",
"ignore-loader": "^0.1.1",
"jasmine-core": "3.10.1",
"jquery": "^3.3.1",
@ -68,24 +68,27 @@
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.7.0",
"karma-junit-reporter": "^2.0.1",
"karma-phantomjs-launcher": "^1.0.4",
"karma-spec-reporter": "0.0.32",
"moment": "^2.14.1",
"phantomjs-prebuilt": "^2.1.16",
"ts-loader": "^6.2.2",
"typedoc": "^0.15.0",
"typedoc": "^0.23.23",
"typescript": "~4.6.0",
"webpack": "^4.44.2",
"webpack-stream": "^5.2.1",
"webpack": "^5.75.0",
"webpack-stream": "^7.0.0",
"yargs": "^16.1.0"
},
"dependencies": {
"http-post-message": "^0.2",
"powerbi-models": "^1.11.0",
"powerbi-models": "^1.12.3",
"powerbi-router": "^0.1",
"window-post-message-proxy": "^0.2"
},
"publishConfig": {
"tag": "beta"
},
"overrides": {
"glob-parent": "^6.0.2",
"lodash.template": "^4.5.0",
"micromatch": "^4.0.5"
}
}

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

@ -3,7 +3,7 @@
/** @ignore *//** */
const config = {
version: '2.21.1',
version: '2.22.2',
type: 'js'
};

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

@ -3,7 +3,7 @@
import { IReportCreateConfiguration, IError, validateCreateReport } from 'powerbi-models';
import { Service } from './service';
import { Embed, IEmbedConfigurationBase, IEmbedConfiguration } from './embed';
import { Embed, IEmbedConfigurationBase, IEmbedConfiguration, ISessionHeaders } from './embed';
import * as utils from './util';
/**
@ -14,6 +14,14 @@ import * as utils from './util';
* @extends {Embed}
*/
export class Create extends Embed {
/**
* Gets or sets the configuration settings for creating report.
*
* @type {IReportCreateConfiguration}
* @hidden
*/
createConfig: IReportCreateConfiguration;
/*
* @hidden
*/
@ -109,4 +117,39 @@ export class Create extends Embed {
return datasetId;
}
/**
* Sends create configuration data.
*
* ```javascript
* create ({
* datasetId: '5dac7a4a-4452-46b3-99f6-a25915e0fe55',
* accessToken: 'eyJ0eXA ... TaE2rTSbmg',
* ```
*
* @hidden
* @returns {Promise<void>}
*/
async create(): Promise<void> {
const errors = validateCreateReport(this.createConfig);
if (errors) {
throw errors;
}
try {
const headers: ISessionHeaders = {
uid: this.config.uniqueId,
sdkSessionId: this.service.getSdkSessionId()
};
if (!!this.eventHooks?.accessTokenProvider) {
headers.tokenProviderSupplied = true;
}
const response = await this.service.hpm.post<void>("/report/create", this.createConfig, headers, this.iframe.contentWindow);
return response.body;
} catch (response) {
throw response.body;
}
}
}

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

@ -5,7 +5,7 @@ import * as models from 'powerbi-models';
import * as sdkConfig from './config';
import { EmbedUrlNotSupported } from './errors';
import { ICustomEvent, IEvent, IEventHandler, Service } from './service';
import { addParamToUrl, assign, autoAuthInEmbedUrl, createRandomString, getTimeDiffInMilliseconds, remove } from './util';
import { addParamToUrl, assign, autoAuthInEmbedUrl, createRandomString, getTimeDiffInMilliseconds, remove, isCreate } from './util';
declare global {
interface Document {
@ -48,6 +48,8 @@ export type ITileEmbedConfiguration = models.ITileEmbedConfiguration;
export type IQnaEmbedConfiguration = models.IQnaEmbedConfiguration;
export type IQuickCreateConfiguration = models.IQuickCreateConfiguration;
export type ILocaleSettings = models.ILocaleSettings;
export type IQnaSettings = models.IQnaSettings;
@ -175,14 +177,6 @@ export abstract class Embed {
*/
bootstrapConfig: IBootstrapEmbedConfiguration;
/**
* Gets or sets the configuration settings for creating report.
*
* @type {models.IReportCreateConfiguration}
* @hidden
*/
createConfig: models.IReportCreateConfiguration;
/**
* Url used in the load request.
*
@ -246,7 +240,7 @@ export abstract class Embed {
this.populateConfig(config, isBootstrap);
if (this.embedtype === 'create') {
if (isCreate(this.embedtype)) {
this.setIframe(false /* set EventListener to call create() on 'load' event*/, phasedRender, isBootstrap);
} else {
this.setIframe(true /* set EventListener to call load() on 'load' event*/, phasedRender, isBootstrap);
@ -254,39 +248,13 @@ export abstract class Embed {
}
/**
* Sends createReport configuration data.
*
* ```javascript
* createReport({
* datasetId: '5dac7a4a-4452-46b3-99f6-a25915e0fe55',
* accessToken: 'eyJ0eXA ... TaE2rTSbmg',
* ```
* Create is not supported by default
*
* @hidden
* @param {models.IReportCreateConfiguration} config
* @returns {Promise<void>}
*/
async createReport(config: models.IReportCreateConfiguration): Promise<void> {
const errors = models.validateCreateReport(config);
if (errors) {
throw errors;
}
try {
const headers: ISessionHeaders = {
uid: this.config.uniqueId,
sdkSessionId: this.service.getSdkSessionId()
};
if (!!this.eventHooks?.accessTokenProvider) {
headers.tokenProviderSupplied = true;
}
const response = await this.service.hpm.post<void>("/report/create", config, headers, this.iframe.contentWindow);
return response.body;
} catch (response) {
throw response.body;
}
create(): Promise<void> {
throw new Error(`no create support`);
}
/**
@ -491,7 +459,7 @@ export abstract class Embed {
throw new Error("Access token cannot be empty");
}
let embedType = this.config.type;
embedType = (embedType === 'create' || embedType === 'visual' || embedType === 'qna') ? 'report' : embedType;
embedType = (embedType === 'create' || embedType === 'visual' || embedType === 'qna' || embedType === 'quickCreate') ? 'report' : embedType;
try {
const response = await this.service.hpm.post<void>('/' + embedType + '/token', accessToken, { uid: this.config.uniqueId }, this.iframe.contentWindow);
@ -813,7 +781,7 @@ export abstract class Embed {
this.element.addEventListener('ready', this.frontLoadHandler, false);
}
} else {
this.iframe.addEventListener('load', () => this.createReport(this.createConfig), false);
this.iframe.addEventListener('load', () => this.create(), false);
}
}

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

@ -31,6 +31,7 @@ export {
IReportEmbedConfiguration,
IDashboardEmbedConfiguration,
ITileEmbedConfiguration,
IQuickCreateConfiguration,
Embed,
ILocaleSettings,
IEmbedSettings,
@ -48,6 +49,9 @@ export {
export {
VisualDescriptor
} from './visualDescriptor';
export {
QuickCreate
} from './quickCreate';
export {
BasicFilterBuilder,
AdvancedFilterBuilder,

119
src/quickCreate.ts Normal file
Просмотреть файл

@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { IError, IQuickCreateConfiguration, validateQuickCreate } from 'powerbi-models';
import { Service } from './service';
import { Embed, IEmbedConfigurationBase, ISessionHeaders } from './embed';
/**
* A Power BI Quick Create component
*
* @export
* @class QuickCreate
* @extends {Embed}
*/
export class QuickCreate extends Embed {
/**
* Gets or sets the configuration settings for creating report.
*
* @type {IQuickCreateConfiguration}
* @hidden
*/
createConfig: IQuickCreateConfiguration;
/*
* @hidden
*/
constructor(service: Service, element: HTMLElement, config: IQuickCreateConfiguration, phasedRender?: boolean, isBootstrap?: boolean) {
super(service, element, config, /* iframe */ undefined, phasedRender, isBootstrap);
service.router.post(`/reports/${this.config.uniqueId}/eventHooks/:eventName`, async (req, _res) => {
switch (req.params.eventName) {
case "newAccessToken":
req.body = req.body || {};
req.body.report = this;
await service.invokeSDKHook(this.eventHooks?.accessTokenProvider, req, _res);
break;
default:
break;
}
});
}
/**
* Override the getId abstract function
* QuickCreate does not need any ID
*
* @returns {string}
*/
getId(): string {
return null;
}
/**
* Validate create report configuration.
*/
validate(config: IEmbedConfigurationBase): IError[] {
return validateQuickCreate(config);
}
/**
* Handle config changes.
*
* @hidden
* @returns {void}
*/
configChanged(isBootstrap: boolean): void {
if (isBootstrap) {
return;
}
this.createConfig = this.config as IQuickCreateConfiguration;
}
/**
* @hidden
* @returns {string}
*/
getDefaultEmbedUrlEndpoint(): string {
return "quickCreate";
}
/**
* Sends quickCreate configuration data.
*
* ```javascript
* quickCreate({
* accessToken: 'eyJ0eXA ... TaE2rTSbmg',
* datasetCreateConfig: {}})
* ```
*
* @hidden
* @param {IQuickCreateConfiguration} createConfig
* @returns {Promise<void>}
*/
async create(): Promise<void> {
const errors = validateQuickCreate(this.createConfig);
if (errors) {
throw errors;
}
try {
const headers: ISessionHeaders = {
uid: this.config.uniqueId,
sdkSessionId: this.service.getSdkSessionId()
};
if (!!this.eventHooks?.accessTokenProvider) {
headers.tokenProviderSupplied = true;
}
const response = await this.service.hpm.post<void>("/quickcreate", this.createConfig, headers, this.iframe.contentWindow);
return response.body;
} catch (response) {
throw response.body;
}
}
}

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

@ -40,7 +40,6 @@ import { IFilterable } from './ifilterable';
import { Page } from './page';
import { BookmarksManager } from './bookmarksManager';
import { VisualDescriptor } from './visualDescriptor';
import * as assert from 'assert';
/**
* A Report node within a report hierarchy
@ -65,7 +64,7 @@ export interface IReportNode {
*/
export class Report extends Embed implements IReportNode, IFilterable {
/** @hidden */
static allowedEvents = ["filtersApplied", "pageChanged", "commandTriggered", "swipeStart", "swipeEnd", "bookmarkApplied", "dataHyperlinkClicked", "visualRendered", "visualClicked", "selectionChanged", "renderingStarted"];
static allowedEvents = ["filtersApplied", "pageChanged", "commandTriggered", "swipeStart", "swipeEnd", "bookmarkApplied", "dataHyperlinkClicked", "visualRendered", "visualClicked", "selectionChanged", "renderingStarted", "blur"];
/** @hidden */
static reportIdAttribute = 'powerbi-report-id';
/** @hidden */
@ -111,7 +110,6 @@ export class Report extends Embed implements IReportNode, IFilterable {
break;
default:
assert(false, `${req.params.eventName} eventHook is not supported`);
break;
}
});
@ -1179,4 +1177,63 @@ export class Report extends Embed implements IReportNode, IFilterable {
async setZoom(zoomLevel: number): Promise<void> {
await this.updateSettings({ zoomLevel: zoomLevel });
}
/**
* Closes all open context menus and tooltips.
*
* ```javascript
* report.closeAllOverlays()
* .then(() => {
* ...
* });
* ```
*
* @returns {Promise<void>}
*/
async closeAllOverlays(): Promise<void> {
if (isRDLEmbed(this.config.embedUrl)) {
return Promise.reject(APINotSupportedForRDLError);
}
try {
const response = await this.service.hpm.post<void>('/report/closeAllOverlays', null, { uid: this.config.uniqueId }, this.iframe.contentWindow);
return response.body;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Clears selected not popped out visuals, if flag is passed, all visuals selections will be cleared.
*
* ```javascript
* report.clearSelectedVisuals()
* .then(() => {
* ...
* });
* ```
*
* @param {Boolean} [clearPopOutState=false]
* If false / undefined visuals selection will not be cleared if one of visuals
* is in popped out state (in focus, show as table, spotlight...)
* @returns {Promise<void>}
*/
async clearSelectedVisuals(clearPopOutState?: boolean): Promise<void> {
clearPopOutState = clearPopOutState === true;
if (isRDLEmbed(this.config.embedUrl)) {
return Promise.reject(APINotSupportedForRDLError);
}
try {
const response = await this.service.hpm.post<void>(
`/report/clearSelectedVisuals/${clearPopOutState.toString()}`,
null,
{ uid: this.config.uniqueId },
this.iframe.contentWindow
);
return response.body;
} catch (error) {
return Promise.reject(error);
}
}
}

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

@ -7,7 +7,7 @@
import { WindowPostMessageProxy } from 'window-post-message-proxy';
import { HttpPostMessage } from 'http-post-message';
import { Router, IExtendedRequest, Response as IExtendedResponse } from 'powerbi-router';
import { IPage, IReportCreateConfiguration } from 'powerbi-models';
import { IPage, IQuickCreateConfiguration, IReportCreateConfiguration } from 'powerbi-models';
import {
Embed,
IBootstrapEmbedConfiguration,
@ -27,6 +27,7 @@ import { Page } from './page';
import { Qna } from './qna';
import { Visual } from './visual';
import * as utils from './util';
import { QuickCreate } from './quickCreate';
import * as sdkConfig from './config';
export interface IEvent<T> {
@ -94,6 +95,11 @@ export interface IService {
export type IComponentEmbedConfiguration = IReportEmbedConfiguration | IDashboardEmbedConfiguration | ITileEmbedConfiguration | IVisualEmbedConfiguration | IQnaEmbedConfiguration;
/**
* @hidden
*/
export type EmbedComponentFactory = (service: Service, element: HTMLElement, config: IEmbedConfigurationBase, phasedRender?: boolean, isBootstrap?: boolean) => Embed;
/**
* The Power BI Service embed component, which is the entry point to embed all other Power BI components into your application
*
@ -133,7 +139,7 @@ export class Service implements IService {
/** The Configuration object for the service*/
private config: IServiceConfiguration;
/** A list of Dashboard, Report and Tile components that have been embedded using this service instance. */
/** A list of Power BI components that have been embedded using this service instance. */
private embeds: Embed[];
/** TODO: Look for way to make hpm private without sacrificing ease of maintenance. This should be private but in embed needs to call methods.
@ -149,6 +155,11 @@ export class Service implements IService {
router: Router;
private uniqueSessionId: string;
/**
* @hidden
*/
private registeredComponents: { [componentType: string]: EmbedComponentFactory } = {};
/**
* Creates an instance of a Power BI Service.
*
@ -277,6 +288,23 @@ export class Service implements IService {
return component;
}
/**
* Creates new dataset
*
* @param {HTMLElement} element
* @param {IEmbedConfiguration} [config={}]
* @returns {Embed}
*/
quickCreate(element: HTMLElement, config: IQuickCreateConfiguration): Embed {
config.type = 'quickCreate';
const powerBiElement = element as IPowerBiElement;
const component = new QuickCreate(this, powerBiElement, config);
powerBiElement.powerBiEmbed = component;
this.addOrOverwriteEmbed(component, element);
return component;
}
/**
* TODO: Add a description here
*
@ -377,6 +405,8 @@ export class Service implements IService {
* @private
* @param {IPowerBiElement} element
* @param {IEmbedConfigurationBase} config
* @param {boolean} phasedRender
* @param {boolean} isBootstrap
* @returns {Embed}
* @hidden
*/
@ -390,18 +420,40 @@ export class Service implements IService {
// Saves the type as part of the configuration so that it can be referenced later at a known location.
config.type = componentType;
const Component = utils.find((embedComponent) => componentType === embedComponent.type.toLowerCase(), Service.components);
if (!Component) {
throw new Error(`Attempted to embed component of type: ${componentType} but did not find any matching component. Please verify the type you specified is intended.`);
}
const component = new Component(this, element, config, phasedRender, isBootstrap);
const component = this.createEmbedComponent(componentType, element, config, phasedRender, isBootstrap);
element.powerBiEmbed = component;
this.addOrOverwriteEmbed(component, element);
return component;
}
/**
* Given component type, creates embed component instance
*
* @private
* @param {string} componentType
* @param {HTMLElement} element
* @param {IEmbedConfigurationBase} config
* @param {boolean} phasedRender
* @param {boolean} isBootstrap
* @returns {Embed}
* @hidden
*/
private createEmbedComponent(componentType: string, element: HTMLElement, config: IEmbedConfigurationBase, phasedRender?: boolean, isBootstrap?: boolean): Embed {
const Component = utils.find((embedComponent) => componentType === embedComponent.type.toLowerCase(), Service.components);
if (Component) {
return new Component(this, element, config, phasedRender, isBootstrap);
}
// If component type is not legacy, search in registered components
const registeredComponent = utils.find((registeredComponentType) => componentType.toLowerCase() === registeredComponentType.toLowerCase(), Object.keys(this.registeredComponents));
if (!registeredComponent) {
throw new Error(`Attempted to embed component of type: ${componentType} but did not find any matching component. Please verify the type you specified is intended.`);
}
return this.registeredComponents[registeredComponent](this, element, config, phasedRender, isBootstrap);
}
/**
* Given an element that already contains an embed component, load with a new configuration.
*
@ -433,7 +485,7 @@ export class Service implements IService {
/**
* When loading report after create we want to use existing Iframe to optimize load period
*/
if (config.type === "report" && component.config.type === "create") {
if (config.type === "report" && utils.isCreate(component.config.type)) {
const report = new Report(this, element, config, /* phasedRender */ false, /* isBootstrap */ false, element.powerBiEmbed.iframe);
component.populateConfig(config, /* isBootstrap */ false);
report.load();
@ -641,8 +693,45 @@ export class Service implements IService {
* @param {string} type
* @returns {void}
*/
setSdkInfo(type: string, version: string): void {
this.hpm.defaultHeaders['x-sdk-type'] = type;
this.hpm.defaultHeaders['x-sdk-wrapper-version'] = version;
setSdkInfo(type: string, version: string): void {
this.hpm.defaultHeaders['x-sdk-type'] = type;
this.hpm.defaultHeaders['x-sdk-wrapper-version'] = version;
}
/**
* API for registering external components
*
* @hidden
* @param {string} componentType
* @param {EmbedComponentFactory} embedComponentFactory
* @param {string[]} routerEventUrls
*/
register(componentType: string, embedComponentFactory: EmbedComponentFactory, routerEventUrls: string[]): void {
if (utils.find((embedComponent) => componentType.toLowerCase() === embedComponent.type.toLowerCase(), Service.components)) {
throw new Error('The component name is reserved. Cannot register a component with this name.');
}
if (utils.find((registeredComponentType) => componentType.toLowerCase() === registeredComponentType.toLowerCase(), Object.keys(this.registeredComponents))) {
throw new Error('A component with this type is already registered.');
}
this.registeredComponents[componentType] = embedComponentFactory;
routerEventUrls.forEach(url => {
if (!url.includes(':uniqueId') || !url.includes(':eventName')) {
throw new Error('Invalid router event URL');
}
this.router.post(url, (req, _res) => {
const event: IEvent<any> = {
type: componentType,
id: req.params.uniqueId as string,
name: req.params.eventName as string,
value: req.body
};
this.handleEvent(event);
});
});
}
}

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

@ -212,3 +212,14 @@ export function getRandomValue(): number {
export function getTimeDiffInMilliseconds(start: Date, end: Date): number {
return Math.abs(start.getTime() - end.getTime());
}
/**
* Checks if the embed type is for create
*
* @export
* @param {string} embedType
* @returns {boolean}
*/
export function isCreate(embedType: string): boolean {
return embedType === 'create' || embedType === 'quickcreate';
}

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

@ -7,6 +7,7 @@ import * as embed from '../src/embed';
import * as report from '../src/report';
import * as visual from '../src/visual';
import * as create from '../src/create';
import * as quickCreate from '../src/quickCreate';
import * as dashboard from '../src/dashboard';
import * as page from '../src/page';
import * as sdkConfig from '../src/config';
@ -2135,7 +2136,7 @@ describe('SDK-to-HPM', function () {
});
});
describe('createReport', function () {
describe('create', function () {
let createElement: HTMLDivElement;
let create: create.Create;
@ -2164,7 +2165,7 @@ describe('SDK-to-HPM', function () {
createElement.remove();
});
it('create.createReport() sends POST /report/create with configuration in body', async function () {
it('create.create() sends POST /report/create with configuration in body', async function () {
// Arrange
const testData = {
createConfiguration: {
@ -2177,15 +2178,16 @@ describe('SDK-to-HPM', function () {
};
spyHpm.post.and.returnValue(Promise.resolve(testData.response));
create.createConfig = testData.createConfiguration;
// Act
await create.createReport(testData.createConfiguration);
await create.create();
// Assert
expect(spyHpm.post).toHaveBeenCalledWith('/report/create', testData.createConfiguration, { uid: createUniqueId, sdkSessionId: sdkSessionId, tokenProviderSupplied: true }, jasmine.any(Object));
});
it('create.createReport() returns promise that rejects with validation error if the create configuration is invalid', async function () {
it('create.create() returns promise that rejects with validation error if the create configuration is invalid', async function () {
// Arrange
const testData = {
createConfiguration: {
@ -2204,7 +2206,8 @@ describe('SDK-to-HPM', function () {
spyHpm.post.and.callFake(() => Promise.reject(testData.errorResponse));
try {
// Act
await create.createReport(testData.createConfiguration);
create.createConfig = testData.createConfiguration;
await create.create();
} catch (error) {
// Assert
@ -2213,7 +2216,7 @@ describe('SDK-to-HPM', function () {
}
});
it('create.createReport() returns promise that resolves with null if create report was successful', async function () {
it('create.create() returns promise that resolves with null if create report was successful', async function () {
// Arrange
const testData = {
createConfiguration: {
@ -2230,7 +2233,8 @@ describe('SDK-to-HPM', function () {
spyHpm.post.and.returnValue(Promise.resolve(testData.response));
// Act
const response = await create.createReport(testData.createConfiguration);
create.createConfig = testData.createConfiguration;
const response = await create.create();
// Assert
expect(spyHpm.post).toHaveBeenCalledWith('/report/create', testData.createConfiguration, { uid: createUniqueId, sdkSessionId: sdkSessionId, tokenProviderSupplied: true }, jasmine.any(Object));
expect(response).toEqual(null);
@ -2568,4 +2572,129 @@ describe('SDK-to-HPM', function () {
});
});
});
describe('quickCreate', function () {
let quickCreateElement: HTMLDivElement;
let quickCreate: quickCreate.QuickCreate;
let quickCreateUniqueId = 'uniqueId';
// Arrange
let testData = {
createConfiguration: {
type: 'quickCreate',
accessToken: 'fakeToken',
groupId: undefined,
settings: undefined,
tokenType: models.TokenType.Aad,
theme: undefined,
datasetCreateConfig: {
locale: "fakeLocale",
mashupDocument: "fakeMashup",
},
reportCreationMode: undefined
},
response: {
body: null
}
};
beforeEach(async () => {
quickCreateElement = document.createElement('div');
quickCreateElement.className = 'powerbi-quickCreate-container';
document.body.appendChild(quickCreateElement);
const quickCreateConfiguration = {
accessToken: 'fakeToken',
tokenType: models.TokenType.Aad,
embedUrl: iframeSrc,
datasetCreateConfig: {
locale: "fakeLocale",
mashupDocument: "fakeMashup",
},
eventHooks: { accessTokenProvider: function () { return null; } }
};
spyHpm.post.and.returnValue(Promise.resolve({}));
quickCreate = <quickCreate.QuickCreate>powerbi.quickCreate(quickCreateElement, quickCreateConfiguration);
createUniqueId = quickCreate.config.uniqueId;
const createIframe = quickCreateElement.getElementsByTagName('iframe')[0];
await new Promise<void>((resolve, _reject) => createIframe.addEventListener('load', () => resolve(null)));
spyHpm.post.and.callThrough();
});
afterEach(() => {
powerbi.reset(quickCreateElement);
quickCreateElement.remove();
});
describe('quickCreate', function () {
it('quickCreate.create() sends POST /quickcreate with configuration in body', async function () {
spyHpm.post.and.returnValue(Promise.resolve(testData.response));
quickCreate.createConfig = <models.IQuickCreateConfiguration>testData.createConfiguration;
quickCreateUniqueId = quickCreate.config.uniqueId;
// Act
await quickCreate.create();
// Assert
let expectedHeaders = {
uid: quickCreateUniqueId,
sdkSessionId: sdkSessionId,
tokenProviderSupplied: true
};
expect(spyHpm.post).toHaveBeenCalledWith('/quickcreate', testData.createConfiguration, expectedHeaders, jasmine.any(Object));
});
it('quickCreate.create() returns promise that rejects with validation error if the create configuration is invalid', async function () {
// Arrange
const errorResponse = {
body: {
message: "invalid configuration object"
}
};
spyHpm.post.and.returnValue(Promise.reject(errorResponse));
quickCreate.createConfig = <models.IQuickCreateConfiguration>testData.createConfiguration;
quickCreateUniqueId = quickCreate.config.uniqueId;
spyHpm.post.and.callFake(() => Promise.reject(errorResponse));
try {
// Act
await quickCreate.create();
} catch (error) {
// Assert
let expectedHeaders = {
uid: quickCreateUniqueId,
sdkSessionId: sdkSessionId,
tokenProviderSupplied: true
};
expect(spyHpm.post).toHaveBeenCalledWith('/quickcreate', testData.createConfiguration, expectedHeaders, jasmine.any(Object));
expect(error).toEqual(errorResponse.body);
}
});
it('quickCreate.create() returns promise that resolves with null if quick create was successful', async function () {
spyHpm.post.and.returnValue(Promise.resolve(testData.response));
quickCreate.createConfig = <models.IQuickCreateConfiguration>testData.createConfiguration;
quickCreateUniqueId = quickCreate.config.uniqueId;
// Act
const response = await quickCreate.create();
expect(response).toEqual(null);
// Assert
let expectedHeaders = {
uid: quickCreateUniqueId,
sdkSessionId: sdkSessionId,
tokenProviderSupplied: true
};
expect(spyHpm.post).toHaveBeenCalledWith(
'/quickcreate',
testData.createConfiguration,
expectedHeaders, jasmine.any(Object));
expect(response).toEqual(null);
});
});
});
});

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

@ -1191,4 +1191,53 @@ describe('Protocol', function () {
});
});
});
describe('quickCreate', function () {
describe('create', function () {
it('POST /quickcreate returns 400 if the request is invalid', async function () {
// Arrange
const testData = {
uniqueId: 'uniqueId',
create: {
accessToken: "fakeToken",
}
};
spyApp.validateQuickCreate.and.callFake(() => Promise.reject(null));
// Act
try {
await hpm.post<models.IError>('/quickcreate', testData.create, { uid: testData.uniqueId });
fail("POST to /quickcreate should fail");
} catch (response) {
// Assert
expect(spyApp.validateQuickCreate).toHaveBeenCalledWith(testData.create);
expect(response.statusCode).toEqual(400);
}
});
it('POST /quickCreate returns 202 if the request is valid', async function () {
// Arrange
const testData = {
uniqueId: 'uniqueId',
create: {
accessToken: "fakeToken",
}
};
spyApp.validateQuickCreate.and.returnValue(Promise.resolve(null));
// Act
try {
const response = await hpm.post<void>('/quickcreate', testData.create, { uid: testData.uniqueId });
// Assert
expect(spyApp.validateQuickCreate).toHaveBeenCalledWith(testData.create);
expect(response.statusCode).toEqual(202);
} catch (error) {
console.log("hpm.post failed with", error);
fail("hpm.post");
}
});
});
});
});

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

@ -643,7 +643,7 @@ describe('service', function () {
.appendTo('#powerbi-fixture');
// Act
const report = powerbi.createReport($reportContainer[0], { embedUrl: embedUrl, accessToken: accessToken, datasetId: testDatasetId });
const report = powerbi.createReport($reportContainer[0], { embedUrl: embedUrl, accessToken: accessToken, datasetId: testDatasetId }) as create.Create;
// Assert
expect(report.createConfig.datasetId).toEqual(testDatasetId);
@ -658,7 +658,7 @@ describe('service', function () {
.appendTo('#powerbi-fixture');
// Act
const report = powerbi.createReport($reportContainer[0], { embedUrl: embedUrl, accessToken: accessToken });
const report = powerbi.createReport($reportContainer[0], { embedUrl: embedUrl, accessToken: accessToken }) as create.Create;
// Assert
expect(report.createConfig.datasetId).toEqual(testDatasetId);
@ -675,7 +675,7 @@ describe('service', function () {
.appendTo('#powerbi-fixture');
// Act
const report = powerbi.createReport($reportContainer[0], { embedUrl: embedUrl, accessToken: accessToken, theme: theme });
const report = powerbi.createReport($reportContainer[0], { embedUrl: embedUrl, accessToken: accessToken, theme: theme }) as create.Create;
// Assert
expect(report.createConfig.theme).toEqual(theme);
@ -691,7 +691,7 @@ describe('service', function () {
.appendTo('#powerbi-fixture');
// Act
const report = powerbi.createReport($reportContainer[0], { embedUrl: embedUrl, accessToken: accessToken });
const report = powerbi.createReport($reportContainer[0], { embedUrl: embedUrl, accessToken: accessToken }) as create.Create;
// Assert
expect(report.createConfig.theme).toBeUndefined();
@ -1041,4 +1041,127 @@ describe('service', function () {
expect(report2).toBeUndefined();
});
});
describe('quickCreate', function () {
const embedUrl = `https://app.powerbi.com/quickcreate`;
const accessToken = 'ABC123';
it('happy path', function () {
// Arrange
const component = $('<div></div>')
.appendTo('#powerbi-fixture');
// Act
const attemptCreate = (): void => {
powerbi.quickCreate(component[0], {
embedUrl: embedUrl,
accessToken: accessToken,
datasetCreateConfig: {
locale: "fakeLocale",
mashupDocument: "fakeMashup",
}
});
};
// Assert
expect(attemptCreate).not.toThrowError();
});
it('if attempting to quickCreate without specifying an embed url, throw error', function () {
// Arrange
const component = $('<div></div>')
.appendTo('#powerbi-fixture');
// Act
const attemptCreate = (): void => {
powerbi.quickCreate(component[0], {
embedUrl: null,
accessToken: accessToken,
datasetCreateConfig: {
locale: "fakeLocale",
mashupDocument: "fakeMashup",
}
});
};
// Assert
expect(attemptCreate).toThrowError(Error);
});
it('if attempting to quickCreate without specifying an access token, throw error', function () {
// Arrange
const component = $('<div></div>')
.appendTo('#powerbi-fixture');
const originalToken = powerbi.accessToken;
powerbi.accessToken = undefined;
// Act
const attemptCreate = (): void => {
powerbi.quickCreate(component[0], {
embedUrl: embedUrl,
accessToken: null,
datasetCreateConfig: {
locale: "fakeLocale",
mashupDocument: "fakeMashup",
}
});
};
// Assert
expect(attemptCreate).toThrowError(Error);
// Cleanup
powerbi.accessToken = originalToken;
});
});
describe('register components', function () {
const registeredComponentType = 'fakeType';
const embedConfig = {
type: registeredComponentType,
accessToken: "fakeAccessToken",
embedUrl: "fakeEmbedUrl",
id: "fakeReportId"
};
const createComponentFunc = (service, element, config): report.Report => new report.Report(service, element, config);
const event = {
type: registeredComponentType,
id: 'fakeId',
name: 'fakeName',
value: 'fakeValue'
};
const routerEventUrls = ['/fakeComponent/:uniqueId/events/:eventName'];
it('happy path: register new component and then successfully embed', function () {
powerbi.register(registeredComponentType, createComponentFunc, routerEventUrls);
const myComponent = powerbi.embed(element, embedConfig);
expect(myComponent).toBeDefined();
});
it('should throw error if registering a component with legacy component type', function () {
const attemptEmbed = (): void => {
powerbi.register('report', createComponentFunc, routerEventUrls);
};
expect(attemptEmbed).toThrowError(Error, 'The component name is reserved. Cannot register a component with this name.');
});
it('should throw error if registering a component with existing type', function () {
powerbi.register(registeredComponentType, createComponentFunc, routerEventUrls);
const attemptEmbed = (): void => {
powerbi.register(registeredComponentType, createComponentFunc, routerEventUrls);
};
expect(attemptEmbed).toThrowError(Error, 'A component with this type is already registered.');
});
it('should throw error if registering a component with invalid router event url', function () {
const attemptEmbed = (): void => {
powerbi.register(registeredComponentType, createComponentFunc, ['/fakeComponent/:invalidUniqueId/events/:eventName']);
};
expect(attemptEmbed).toThrowError(Error, 'Invalid router event URL');
});
});
});

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

@ -40,6 +40,7 @@ export interface IApp {
refreshData(): Promise<void>;
exportData(): Promise<void>;
validateCreateReport(config: models.IReportCreateConfiguration): Promise<models.IError[]>;
validateQuickCreate(config: models.IQuickCreateConfiguration): Promise<models.IError[]>;
switchMode(): Promise<void>;
save(): Promise<void>;
saveAs(saveAsParameters: models.ISaveAsParameters): Promise<void>;
@ -84,6 +85,7 @@ export const mockAppSpyObj = {
refreshData: jasmine.createSpy("refreshData").and.returnValue(Promise.resolve(null)),
exportData: jasmine.createSpy("exportData").and.returnValue(Promise.resolve(null)),
validateCreateReport: jasmine.createSpy("validateCreateReport").and.callFake(models.validateCreateReport),
validateQuickCreate: jasmine.createSpy("validateQuickCreate").and.callFake(models.validateQuickCreate),
switchMode: jasmine.createSpy("switchMode").and.returnValue(Promise.resolve(null)),
save: jasmine.createSpy("save").and.returnValue(Promise.resolve(null)),
saveAs: jasmine.createSpy("saveAs").and.returnValue(Promise.resolve(null)),
@ -151,6 +153,8 @@ export const mockAppSpyObj = {
mockAppSpyObj.exportData.and.callThrough();
mockAppSpyObj.validateCreateReport.calls.reset();
mockAppSpyObj.validateCreateReport.and.callThrough();
mockAppSpyObj.validateQuickCreate.calls.reset();
mockAppSpyObj.validateQuickCreate.and.callThrough();
mockAppSpyObj.switchMode.calls.reset();
mockAppSpyObj.switchMode.and.callThrough();
mockAppSpyObj.save.calls.reset();

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

@ -83,6 +83,19 @@ export function setupEmbedMockApp(iframeContentWindow: Window, parentWindow: Win
}
});
/**
* Quick Create
*/
router.post('/quickcreate', (req, res) => {
const createConfig = req.body;
return app.validateQuickCreate(createConfig)
.then(() => {
res.send(202);
}, error => {
res.send(400, error);
});
});
/**
* Report Embed
*/