├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── app.ts ├── application │ ├── controller.ts │ ├── module.ts │ ├── route.ts │ └── template.html ├── components │ ├── powerbi-filter-pane │ │ ├── component.ts │ │ └── template.html │ └── powerbi-page-navigation │ │ ├── component.ts │ │ └── template.html ├── index.html ├── scenario1 │ ├── controller.ts │ ├── module.ts │ ├── route.ts │ └── template.html ├── scenario2 │ ├── controller.ts │ ├── module.ts │ ├── route.ts │ └── template.html ├── scenario3 │ ├── controller.ts │ ├── module.ts │ ├── route.ts │ └── template.html ├── scenario4 │ ├── controller.ts │ ├── module.ts │ ├── route.ts │ └── template.html ├── scenario5 │ ├── controller.ts │ ├── module.ts │ ├── route.ts │ └── template.html ├── scenario6 │ ├── controller.ts │ ├── module.ts │ ├── route.ts │ └── template.html ├── services │ ├── reports.ts │ └── utilities.ts └── styles │ └── app.css ├── gulp └── config.js ├── gulpfile.js ├── package.json ├── tsconfig.json ├── typings.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | typings 3 | dist 4 | .publish 5 | npm-debug.log* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Azure samples 2 | 3 | Thank you for your interest in contributing to Azure samples! 4 | 5 | ## Ways to contribute 6 | 7 | You can contribute to [Azure samples](https://azure.microsoft.com/documentation/samples/) in a few different ways: 8 | 9 | - Submit feedback on [this sample page](https://azure.microsoft.com/documentation/samples/powerbi-angular-client/) whether it was helpful or not. 10 | - Submit issues through [issue tracker](https://github.com/Azure-Samples/powerbi-angular-client/issues) on GitHub. We are actively monitoring the issues and improving our samples. 11 | - If you wish to make code changes to samples, or contribute something new, please follow the [GitHub Forks / Pull requests model](https://help.github.com/articles/fork-a-repo/): Fork the sample repo, make the change and propose it back by submitting a pull request. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # powerbi-angular-client 2 | Sample application which demonstrates using [angular-powerbi](https://github.com/Microsoft/PowerBI-Angular) library within Angular 1.x application. 3 | 4 | ## View Live Demo 5 | [http://azure-samples.github.io/powerbi-angular-client](http://azure-samples.github.io/powerbi-angular-client) 6 | 7 | ## Running this sample locally 8 | 9 | ### Clone the repository 10 | 11 | ``` 12 | git clone https://github.com/Azure-Samples/powerbi-angular-client 13 | ``` 14 | 15 | ### Install dependencies 16 | ``` 17 | npm install 18 | ``` 19 | 20 | ### Run Build 21 | ``` 22 | npm run build 23 | ``` 24 | 25 | ### Serve the files from the root of the repository: 26 | ``` 27 | npm start 28 | ``` 29 | 30 | ### Open `dist/index.html` within the browser: 31 | 32 | Example: `127.0.0.1:8080/dist/` 33 | 34 | ## Deploy this sample to Azure 35 | [![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/) 36 | 37 | ## About the code 38 | See: [angular-powerbi](https://github.com/Microsoft/PowerBI-Angular) for details about usage of module. 39 | 40 | ## More information 41 | We're interested in feedback. Open a [new issue](https://github.com/Azure-Samples/powerbi-angular-client/issues/new) if you have requests or find bugs. 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/app.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import * as angularuirouter from 'angular-ui-router'; 3 | import * as angularpowerbi from 'angular-powerbi'; 4 | 5 | // Services 6 | import reportsProvider from './services/reports'; 7 | import Utilities from './services/utilities'; 8 | 9 | // Pods 10 | import { controller as applicationController, route as applicationRoute } from './application/module'; 11 | import { controller as scenario1Controller, route as scenario1Route } from './scenario1/module'; 12 | import { controller as scenario2Controller, route as scenario2Route } from './scenario2/module'; 13 | import { controller as scenario3Controller, route as scenario3Route } from './scenario3/module'; 14 | import { controller as scenario4Controller, route as scenario4Route } from './scenario4/module'; 15 | import { controller as scenario5Controller, route as scenario5Route } from './scenario5/module'; 16 | import { controller as scenario6Controller, route as scenario6Route } from './scenario6/module'; 17 | import pageNavigationComponent from './components/powerbi-page-navigation/component'; 18 | import filterPaneComponent from './components/powerbi-filter-pane/component'; 19 | 20 | // Config 21 | config['$inject'] = ["$stateProvider", "$urlRouterProvider", "ReportsServiceProvider"]; 22 | function config($stateProvider: angularuirouter.IStateProvider, $urlRouterProvider: angularuirouter.IUrlRouterProvider, ReportsServiceProvider: any) { 23 | 24 | ReportsServiceProvider.setBaseUrl('https://powerbiembedapi.azurewebsites.net'); 25 | 26 | $urlRouterProvider.otherwise('/scenario1'); 27 | 28 | $stateProvider.state(applicationRoute); 29 | $stateProvider.state(scenario1Route); 30 | $stateProvider.state(scenario2Route); 31 | $stateProvider.state(scenario3Route); 32 | $stateProvider.state(scenario4Route); 33 | $stateProvider.state(scenario5Route); 34 | $stateProvider.state(scenario6Route); 35 | } 36 | 37 | angular 38 | .module('app', [ 39 | 'ui.router', 40 | 'powerbi' 41 | ]) 42 | .provider('ReportsService', reportsProvider) 43 | .service('Utilities', Utilities) 44 | .controller('ApplicationController', applicationController) 45 | .controller('Scenario1Controller', scenario1Controller) 46 | .controller('Scenario2Controller', scenario2Controller) 47 | .controller('Scenario3Controller', scenario3Controller) 48 | .controller('Scenario4Controller', scenario4Controller) 49 | .controller('Scenario5Controller', scenario5Controller) 50 | .controller('Scenario6Controller', scenario6Controller) 51 | .directive('powerbiPageNavigation', () => new pageNavigationComponent()) 52 | .directive('powerbiFilterPane', () => new filterPaneComponent()) 53 | .config(config) 54 | ; 55 | -------------------------------------------------------------------------------- /app/application/controller.ts: -------------------------------------------------------------------------------- 1 | export default class controller { 2 | static $inject = [ 3 | '$scope' 4 | ]; 5 | 6 | constructor($scope: ng.IScope) { 7 | } 8 | } -------------------------------------------------------------------------------- /app/application/module.ts: -------------------------------------------------------------------------------- 1 | import controller from './controller'; 2 | import route from './route'; 3 | 4 | export { controller, route }; -------------------------------------------------------------------------------- /app/application/route.ts: -------------------------------------------------------------------------------- 1 | import * as angularuirouter from 'angular-ui-router'; 2 | 3 | const state: angularuirouter.IState = { 4 | name: "application", 5 | abstract: true, 6 | templateUrl: "/app/application/template.html", 7 | controller: "ApplicationController", 8 | controllerAs: "vm" 9 | } 10 | 11 | export default state; -------------------------------------------------------------------------------- /app/application/template.html: -------------------------------------------------------------------------------- 1 |
2 |

Power BI - Sample - Client - Angular

3 |

Demonstrate how to consume Power BI API and render using angular components. PowerBI-Angular

4 | 5 |

Scenarios:

6 | 14 |
15 |
16 | Loading... 17 |
18 |
-------------------------------------------------------------------------------- /app/components/powerbi-filter-pane/component.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | 3 | interface IFiltersNode { 4 | name: string; 5 | filterable: any; 6 | filters: (pbi.models.IBasicFilter | pbi.models.IAdvancedFilter)[]; 7 | nodes: IFiltersNode[]; 8 | } 9 | 10 | export class Controller { 11 | private $scope: ng.IScope; 12 | onAddFilter: Function; 13 | onRefreshFilters: Function; 14 | onRemoveFilter: Function; 15 | report: pbi.Report; 16 | reportTargets: string[] = [ 17 | "Report", 18 | "Page", 19 | "Visual" 20 | ]; 21 | pages: pbi.Page[]; 22 | selectedPage: pbi.Page; 23 | selectedReportTarget: string = this.reportTargets[0]; 24 | 25 | targetTypes: string[] = [ 26 | 'Column', 27 | 'Hierarchy', 28 | 'Measure' 29 | ]; 30 | selectedTargetType: string = this.targetTypes[0]; 31 | 32 | filterTypes: string[] = [ 33 | 'Basic', 34 | 'Advanced' 35 | ] 36 | selectedFilterType: string = this.filterTypes[0]; 37 | 38 | basicOperators: string[] = [ 39 | 'In', 40 | 'NotIn' 41 | ]; 42 | selectedBasicOperator: string = this.basicOperators[0]; 43 | 44 | logicalOperators: string[] = [ 45 | 'And', 46 | 'Or' 47 | ]; 48 | selectedLogicalOperator: string = this.logicalOperators[0]; 49 | 50 | value1: string; 51 | value2: string; 52 | 53 | conditionalOperators: string[] = [ 54 | 'None', 55 | 'LessThan', 56 | 'LessThanOrEqual', 57 | 'GreaterThan', 58 | 'GreaterThanOrEqual', 59 | 'Contains', 60 | 'DoesNotContain', 61 | 'StartsWith', 62 | 'DoesNotStartWith', 63 | 'Is', 64 | 'IsNot', 65 | 'IsBlank', 66 | 'IsNotBlank' 67 | ]; 68 | valueA: string; 69 | conditionalOperatorA: string; 70 | valueB: string; 71 | conditionalOperatorB: string; 72 | 73 | table: string; 74 | column: string; 75 | hierarchy: string; 76 | hierarchyLevel: string; 77 | measure: string; 78 | filtersNode: IFiltersNode; 79 | 80 | static $inject = [ 81 | '$scope' 82 | ] 83 | 84 | constructor( 85 | $scope: ng.IScope 86 | ) { 87 | this.$scope = $scope; 88 | 89 | this.$scope.$watch(() => this.pages, (pages, oldPages) => { 90 | if (pages === oldPages) { 91 | return; 92 | } 93 | 94 | if (Array.isArray(pages) && pages.length > 0) { 95 | this.selectedPage = pages[0]; 96 | } 97 | }); 98 | 99 | this.$scope.$watch(() => this.filtersNode, (filtersNode, oldFiltersNode) => { 100 | if (filtersNode === oldFiltersNode) { 101 | return; 102 | } 103 | 104 | console.log('filtersNode changed'); 105 | }, true); 106 | } 107 | 108 | onSubmit() { 109 | console.log('submit'); 110 | 111 | const data: any = { 112 | target: this.getFilterTypeTarget(), 113 | operator: this.getFilterOperatorAndValues(), 114 | filterable: this.getFilterableTarget() 115 | }; 116 | 117 | let filter: pbi.models.BasicFilter | pbi.models.AdvancedFilter; 118 | 119 | if (data.operator.type === "Basic") { 120 | filter = new pbi.models.BasicFilter(data.target, data.operator.operator, data.operator.values); 121 | } 122 | else if (data.operator.type === "Advanced") { 123 | filter = new pbi.models.AdvancedFilter(data.target, data.operator.operator, data.operator.values); 124 | } 125 | 126 | this.onAddFilter({ $filter: filter.toJSON(), $target: data.filterable }); 127 | } 128 | 129 | refreshClicked() { 130 | console.log('refresh'); 131 | this.onRefreshFilters(); 132 | } 133 | 134 | remove(filter: pbi.models.IFilter, filterableName: string) { 135 | console.log('remove'); 136 | this.onRemoveFilter({ 137 | $filter: filter, 138 | $filterableName: filterableName 139 | }) 140 | .then(() => { 141 | console.log('filter removed'); 142 | }); 143 | } 144 | 145 | private getFilterTypeTarget() { 146 | const target: any = { 147 | table: this.table 148 | }; 149 | 150 | if (this.selectedTargetType === "Column") { 151 | target.column = this.column; 152 | } 153 | else if (this.selectedTargetType === "Hierarchy") { 154 | target.hierarchy = this.hierarchy; 155 | target.hierarchyLevel = this.hierarchyLevel; 156 | } 157 | else if (this.selectedTargetType === "Measure") { 158 | target.measure = this.measure; 159 | } 160 | 161 | return target; 162 | } 163 | 164 | private getFilterOperatorAndValues() { 165 | const operatorAndValues: any = { 166 | type: this.selectedFilterType 167 | }; 168 | 169 | if (this.selectedFilterType === "Basic") { 170 | operatorAndValues.operator = this.selectedBasicOperator; 171 | operatorAndValues.values = [this.value1, this.value2]; 172 | } 173 | else if (this.selectedFilterType === "Advanced") { 174 | operatorAndValues.operator = this.selectedLogicalOperator; 175 | operatorAndValues.values = [ 176 | { 177 | operator: this.conditionalOperatorA, 178 | value: this.valueA 179 | }, 180 | { 181 | operator: this.conditionalOperatorB, 182 | value: this.valueB 183 | } 184 | ]; 185 | } 186 | 187 | return operatorAndValues; 188 | } 189 | 190 | private getFilterableTarget() { 191 | var target: pbi.IFilterable = this.report; 192 | 193 | if (this.selectedReportTarget === "Page") { 194 | target = this.selectedPage; 195 | } 196 | else if (this.selectedReportTarget === "Visual") { 197 | throw new Error(`Abilty to apply filters to visuals is not implemented yet`); 198 | } 199 | 200 | return target; 201 | } 202 | } 203 | 204 | export default class Directive { 205 | restrict = "E"; 206 | templateUrl = "/app/components/powerbi-filter-pane/template.html"; 207 | scope = { 208 | report: "=", 209 | pages: "=", 210 | filtersNode: "=", 211 | onAddFilter: "&", 212 | onRefreshFilters: "&", 213 | onRemoveFilter: "&" 214 | }; 215 | controller = Controller; 216 | bindToController = true; 217 | controllerAs = "vm"; 218 | } -------------------------------------------------------------------------------- /app/components/powerbi-filter-pane/template.html: -------------------------------------------------------------------------------- 1 |
2 |

Custom Filter Pane

3 | 4 |
5 | Target Type: 6 |
7 | 10 |
11 | 12 |
13 | 14 | 15 |
16 |
17 | 18 | 19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 |
35 | Operator Type: 36 |
37 | 41 |
42 | 43 |
44 |
Basic Operators
45 |
46 | 48 |
49 | 50 |
Values
51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 |
60 | 61 |
62 |
Advanced Operators
63 | 64 |

Logical Operator

65 |
66 | 68 |
69 | 70 |
71 |
72 |

Value

73 |
74 | 75 |
76 |

Condition Operator

77 | 79 |
80 | 81 |
82 |

Value

83 |
84 | 85 |
86 |

Condition Operator

87 | 89 |
90 |
91 |
92 |
93 | 94 |
95 | Report Target: 96 |
97 |
98 | 102 |
103 |
104 | 105 |
106 |
107 | 109 |
110 |
111 |
112 | 113 |
114 | 115 |
116 |
117 | 118 |
119 |

Applied Filters

120 |

121 | 122 |

123 | 124 |

Report Level

125 |

{{vm.filtersNode.name}}

126 |
127 |
128 | 129 |
130 | {{filter | json}} 131 |
132 |
133 |
134 | 135 |

Page Level

136 |
137 |

{{node.name}}

138 |
139 |
140 | 141 |
142 | {{filter | json}} 143 |
144 |
145 |
146 |
147 | 148 |

Visual Level

149 |

N/A

150 |
-------------------------------------------------------------------------------- /app/components/powerbi-page-navigation/component.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | 3 | export class Controller { 4 | private $scope: ng.IScope; 5 | activePage: pbi.Page; 6 | cycleIsEnabled = false; 7 | onCycleClicked: Function; 8 | onNextClicked: Function; 9 | onPageClicked: Function; 10 | onPreviousClicked: Function; 11 | pages: pbi.Page[]; 12 | 13 | static $inject = [ 14 | '$scope' 15 | ] 16 | 17 | constructor( 18 | $scope: ng.IScope 19 | ) { 20 | this.$scope = $scope; 21 | } 22 | 23 | cyclePageClicked() { 24 | this.cycleIsEnabled = !this.cycleIsEnabled; 25 | this.onCycleClicked(); 26 | } 27 | 28 | nextPageClicked() { 29 | this.onNextClicked(); 30 | } 31 | 32 | pageClicked(page: pbi.Page) { 33 | this.onPageClicked({ $page: page }); 34 | } 35 | 36 | previousPageClicked() { 37 | this.onPreviousClicked(); 38 | } 39 | } 40 | 41 | export default class Directive { 42 | restrict = "E"; 43 | // template = "
ABC
"; 44 | templateUrl = "/app/components/powerbi-page-navigation/template.html"; 45 | scope = { 46 | activePage: "=", 47 | pages: "=", 48 | onCycleClicked: "&", 49 | onNextClicked: "&", 50 | onPageClicked: "&", 51 | onPreviousClicked: "&" 52 | }; 53 | controller = Controller; 54 | bindToController = true; 55 | controllerAs = "vm"; 56 | } -------------------------------------------------------------------------------- /app/components/powerbi-page-navigation/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | No pages loaded 6 |
7 | 8 | 9 |
-------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Power BI - Sample - Client - Angular 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | Loading... 26 |
27 | 28 | -------------------------------------------------------------------------------- /app/scenario1/controller.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | 3 | export default class controller { 4 | model: pbi.IEmbedConfiguration 5 | title: string; 6 | 7 | static $inject = [ 8 | 'scenario1model' 9 | ]; 10 | 11 | constructor( 12 | model: pbi.IEmbedConfiguration 13 | ) { 14 | this.model = model; 15 | this.title = 'Scenario 1'; 16 | } 17 | } -------------------------------------------------------------------------------- /app/scenario1/module.ts: -------------------------------------------------------------------------------- 1 | import controller from './controller'; 2 | import route from './route'; 3 | 4 | export { controller, route }; -------------------------------------------------------------------------------- /app/scenario1/route.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularuirouter from 'angular-ui-router'; 3 | import { ReportsService } from '../services/reports'; 4 | 5 | const modelResolver = (ReportsService: ReportsService): ng.IPromise => { 6 | return ReportsService.findById('c52af8ab-0468-4165-92af-dc39858d66ad'); 7 | }; 8 | modelResolver["$inject"] = ['ReportsService']; 9 | 10 | const state: angularuirouter.IState = { 11 | name: "application.scenario1", 12 | parent: "application", 13 | url: "^/scenario1", 14 | views: { 15 | 'main': { 16 | templateUrl: "/app/scenario1/template.html", 17 | controller: "Scenario1Controller", 18 | controllerAs: "vm" 19 | } 20 | }, 21 | resolve: { 22 | scenario1model: modelResolver 23 | } 24 | } 25 | 26 | export default state; -------------------------------------------------------------------------------- /app/scenario1/template.html: -------------------------------------------------------------------------------- 1 |

Static Embed

2 |

Report to embed is known by the developer: {{vm.model.id}}

3 | 4 | -------------------------------------------------------------------------------- /app/scenario2/controller.ts: -------------------------------------------------------------------------------- 1 | import { ReportsService } from '../services/reports'; 2 | import Utilities from '../services/utilities'; 3 | import * as pbi from 'powerbi-client'; 4 | 5 | export default class controller { 6 | $q: ng.IQService; 7 | report: pbi.Embed; 8 | ReportsService: ReportsService; 9 | reports: any[]; 10 | searchInput: string; 11 | title: string; 12 | 13 | static $inject = [ 14 | '$q', 15 | '$scope', 16 | 'ReportsService', 17 | 'Utilities' 18 | ]; 19 | 20 | constructor($q: ng.IQService, $scope: ng.IScope, ReportsService: ReportsService, Utilities: Utilities) { 21 | this.$q = $q; 22 | this.ReportsService = ReportsService; 23 | this.title = 'Scenario 2'; 24 | this.reports = []; 25 | 26 | const debouncedSearchInput = Utilities.debounce(this.searchInputDidChange.bind(this), 500); 27 | 28 | $scope.$watch(() => this.searchInput, (searchInput, oldInput) => { 29 | // Guard against initializer 30 | if (searchInput === oldInput) { 31 | return; 32 | } 33 | 34 | debouncedSearchInput(searchInput); 35 | }); 36 | } 37 | 38 | embedReport(report: pbi.IEmbedConfiguration): void { 39 | const reportPromise: ng.IPromise = new this.$q((resolve, reject) => { 40 | if (!report.accessToken) { 41 | resolve(this.ReportsService.findById(report.id)); 42 | } 43 | else { 44 | resolve(report); 45 | } 46 | }); 47 | 48 | reportPromise 49 | .then(embedConfiguration => { 50 | this.report = embedConfiguration; 51 | }); 52 | } 53 | 54 | resetClicked() { 55 | this.report = null; 56 | } 57 | 58 | searchInputDidChange(input: string): void { 59 | this.ReportsService.findByName(input) 60 | .then(reports => { 61 | this.reports = reports; 62 | }); 63 | } 64 | 65 | showAllClicked() { 66 | this.ReportsService.findAll() 67 | .then(reports => { 68 | this.reports = reports; 69 | }); 70 | } 71 | } -------------------------------------------------------------------------------- /app/scenario2/module.ts: -------------------------------------------------------------------------------- 1 | import controller from './controller'; 2 | import route from './route'; 3 | 4 | export { controller, route }; -------------------------------------------------------------------------------- /app/scenario2/route.ts: -------------------------------------------------------------------------------- 1 | import * as angularuirouter from 'angular-ui-router'; 2 | 3 | const state: angularuirouter.IState = { 4 | name: "application.scenario2", 5 | parent: "application", 6 | url: "^/scenario2", 7 | views: { 8 | 'main': { 9 | templateUrl: "/app/scenario2/template.html", 10 | controller: "Scenario2Controller", 11 | controllerAs: "vm" 12 | } 13 | } 14 | } 15 | 16 | export default state; -------------------------------------------------------------------------------- /app/scenario2/template.html: -------------------------------------------------------------------------------- 1 |

Dynamic Embed

2 |

Report to embed is chosen by the user.

3 | 4 |

Example: Search reports by name, render selected report.

5 | Hint: Type 'Re' in the text field and search to find all reports that begin with 'Re' 6 | 7 |
8 |
9 | 10 | 11 |
12 | 13 | 14 |
15 | 18 |
19 |
20 | 21 |

Total Results: {{vm.reports.length}}

22 |
    23 |
  1. 24 | {{report.name}} 25 | 26 |
  2. 27 |
28 |

29 | 30 |

31 | -------------------------------------------------------------------------------- /app/scenario3/controller.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularPbi from 'angular-powerbi'; 3 | 4 | export default class controller { 5 | $q: ng.IQService; 6 | $scope: ng.IScope; 7 | $interval: ng.IIntervalService; 8 | 9 | activePage: pbi.Page; 10 | cycleIntervalPromise: ng.IPromise; 11 | cycleIsEnabled = false; 12 | embedConfiguration: pbi.IEmbedConfiguration; 13 | pages: pbi.Page[]; 14 | report: pbi.Report; 15 | title: string; 16 | 17 | static $inject = [ 18 | '$q', 19 | '$scope', 20 | '$interval', 21 | 'scenario3model', 22 | 'PowerBiService' 23 | ]; 24 | 25 | constructor( 26 | $q: ng.IQService, 27 | $scope: ng.IScope, 28 | $interval: ng.IIntervalService, 29 | embedConfiguration: pbi.IEmbedConfiguration, 30 | powerBiService: pbi.service.Service 31 | ) { 32 | this.$q = $q; 33 | this.$scope = $scope; 34 | this.$interval = $interval; 35 | 36 | this.pages = []; 37 | this.title = 'Scenario 3'; 38 | 39 | this.embedConfiguration = angular.extend(embedConfiguration, { 40 | settings: { 41 | navContentPaneEnabled: false 42 | } 43 | }); 44 | } 45 | 46 | cyclePageClicked() { 47 | console.log(`cyclePageClicked`); 48 | this.toggleCycle(); 49 | } 50 | 51 | nextPageClicked() { 52 | console.log(`nextPageClicked`); 53 | this.changePage(true); 54 | } 55 | 56 | pageClicked(page: pbi.Page) { 57 | page.setActive(); 58 | } 59 | 60 | previousPageClicked() { 61 | console.log(`previousPageClicked`); 62 | this.changePage(false); 63 | } 64 | 65 | onEmbedded(report: pbi.Report) { 66 | console.log(`Report embedded: `, report); 67 | this.report = report; 68 | 69 | report.on('loaded', event => { 70 | report.getPages() 71 | .then(pages => { 72 | this.$scope.$apply(() => { 73 | this.pages = pages; 74 | if (pages.length > 0) { 75 | this.activePage = pages[0]; 76 | } 77 | }); 78 | }); 79 | }); 80 | 81 | report.on<{ newPage: pbi.Page }>('pageChanged', event => { 82 | const page = event.detail.newPage; 83 | this.$scope.$apply(() => { 84 | this.updateActivePage(page); 85 | }); 86 | }); 87 | } 88 | 89 | updateActivePage(newPage: pbi.Page) { 90 | this.activePage = newPage; 91 | } 92 | 93 | private changePage(forwards: boolean = false) { 94 | let activePageIndex = -1; 95 | this.pages 96 | .some((page, i) => { 97 | if (page.name === this.activePage.name) { 98 | activePageIndex = i; 99 | return true; 100 | } 101 | }); 102 | 103 | if (forwards) { 104 | activePageIndex += 1; 105 | } 106 | else { 107 | activePageIndex -= 1; 108 | } 109 | 110 | if (activePageIndex > this.pages.length - 1) { 111 | activePageIndex = 0; 112 | } 113 | if (activePageIndex < 0) { 114 | activePageIndex = this.pages.length - 1; 115 | } 116 | 117 | this.pages 118 | .some((page, i) => { 119 | if (activePageIndex === i) { 120 | page.setActive(); 121 | return true; 122 | } 123 | }); 124 | } 125 | 126 | private toggleCycle() { 127 | if (this.cycleIsEnabled) { 128 | this.cycleIsEnabled = false; 129 | this.$interval.cancel(this.cycleIntervalPromise); 130 | } 131 | else { 132 | this.cycleIsEnabled = true; 133 | this.cycleIntervalPromise = this.$interval(() => { 134 | console.log('interval called'); 135 | this.changePage(true); 136 | }, 2000); 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /app/scenario3/module.ts: -------------------------------------------------------------------------------- 1 | import controller from './controller'; 2 | import route from './route'; 3 | 4 | export { controller, route }; -------------------------------------------------------------------------------- /app/scenario3/route.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularuirouter from 'angular-ui-router'; 3 | import { ReportsService } from '../services/reports'; 4 | 5 | const modelResolver = (ReportsService: ReportsService): ng.IPromise => { 6 | return ReportsService.findById('c52af8ab-0468-4165-92af-dc39858d66ad'); 7 | }; 8 | modelResolver["$inject"] = ['ReportsService']; 9 | 10 | const state: angularuirouter.IState = { 11 | name: "application.scenario3", 12 | parent: "application", 13 | url: "^/scenario3", 14 | views: { 15 | 'main': { 16 | templateUrl: "/app/scenario3/template.html", 17 | controller: "Scenario3Controller", 18 | controllerAs: "vm" 19 | } 20 | }, 21 | resolve: { 22 | scenario3model: modelResolver 23 | } 24 | } 25 | 26 | export default state; -------------------------------------------------------------------------------- /app/scenario3/template.html: -------------------------------------------------------------------------------- 1 |

Custom Page Navigation

2 |

Page navigation is hidden in the embedded report and recreated by developer to allow custom branding or even automation to tell stories and navigate user.

3 | 4 | 5 | 6 |

7 | {{vm.activePage.displayName}} 8 |

9 | 10 | -------------------------------------------------------------------------------- /app/scenario4/controller.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularPbi from 'angular-powerbi'; 3 | 4 | interface IFiltersNode { 5 | name: string; 6 | filterable: any; 7 | filters: pbi.models.IFilter[]; 8 | nodes: IFiltersNode[]; 9 | } 10 | 11 | export default class Controller { 12 | static predefinedFilter1 = new pbi.models.AdvancedFilter({ 13 | table: "Store", 14 | column: "Name" 15 | }, "Or", 16 | { 17 | operator: "Contains", 18 | value: "Direct" 19 | }, 20 | { 21 | operator: "None", 22 | value: "x" 23 | } 24 | ); 25 | static predefinedFilter2 = new pbi.models.AdvancedFilter({ 26 | table: "Store", 27 | column: "Name" 28 | }, "Or", 29 | { 30 | operator: "Contains", 31 | value: "Wash" 32 | }, 33 | { 34 | operator: "Contains", 35 | value: "Park" 36 | } 37 | ); 38 | static predefinedFilter3 = new pbi.models.AdvancedFilter({ 39 | table: "Store", 40 | column: "Name" 41 | }, "Or", 42 | { 43 | operator: "Contains", 44 | value: "Wash" 45 | }, 46 | { 47 | operator: "Contains", 48 | value: "Park" 49 | } 50 | ); 51 | $q: ng.IQService; 52 | $scope: ng.IScope; 53 | 54 | embedConfiguration: pbi.IEmbedConfiguration; 55 | report: pbi.Report; 56 | reportPages: pbi.Page[]; 57 | filtersNode: IFiltersNode; 58 | selectedRemoveFiltersPage: pbi.Page; 59 | title: string; 60 | 61 | static $inject = [ 62 | '$q', 63 | '$scope', 64 | 'scenario4model', 65 | 'PowerBiService' 66 | ]; 67 | 68 | constructor( 69 | $q: ng.IQService, 70 | $scope: ng.IScope, 71 | embedConfiguration: pbi.IEmbedConfiguration, 72 | powerBiService: pbi.service.Service 73 | ) { 74 | this.$q = $q; 75 | this.$scope = $scope; 76 | 77 | this.title = 'Scenario 4'; 78 | 79 | this.embedConfiguration = angular.extend(embedConfiguration, { 80 | settings: { 81 | filterPaneEnabled: false, 82 | navContentPaneEnabled: true 83 | } 84 | }); 85 | 86 | this.filtersNode = { 87 | name: undefined, 88 | filterable: null, 89 | filters: [], 90 | nodes: [] 91 | }; 92 | } 93 | 94 | onEmbedded(report: pbi.Report) { 95 | this.report = report; 96 | 97 | report.on('loaded', event => { 98 | console.log('report loaded'); 99 | report.getPages() 100 | .then(pages => { 101 | this.$scope.$apply(() => { 102 | this.reportPages = pages; 103 | this.selectedRemoveFiltersPage = this.reportPages[0]; 104 | }); 105 | }); 106 | }); 107 | } 108 | 109 | onFilterAdded(filter: pbi.models.IBasicFilter | pbi.models.IAdvancedFilter, filterable: pbi.IFilterable) { 110 | console.log('onFilterAdded'); 111 | console.log(filter, filterable); 112 | 113 | filterable.setFilters([filter]); 114 | } 115 | 116 | onRefreshFilters() { 117 | console.log('onRefreshFilters'); 118 | this.report.getFilters() 119 | .then(filters => { 120 | this.$scope.$apply(() => { 121 | this.filtersNode.filters = filters; 122 | }); 123 | }); 124 | 125 | const pageNodePromises = this.reportPages 126 | .map(page => { 127 | return page.getFilters() 128 | .then(filters => { 129 | let node: IFiltersNode; 130 | let filteredNodes = this.filtersNode.nodes.filter(node => node.name === page.name); 131 | if (filteredNodes.length === 1) { 132 | node = filteredNodes[0]; 133 | node.filters = filters; 134 | } 135 | else { 136 | const newNode: IFiltersNode = { 137 | name: page.name, 138 | filterable: null, 139 | filters, 140 | nodes: [] 141 | }; 142 | 143 | this.filtersNode.nodes.push(newNode); 144 | } 145 | }); 146 | }); 147 | 148 | Promise.all(pageNodePromises) 149 | .then(() => { 150 | this.$scope.$apply(() => { }); 151 | }); 152 | } 153 | 154 | onRemoveFilter(filterToRemove: pbi.models.IAdvancedFilter | pbi.models.IBasicFilter, filterableName: string): Promise { 155 | console.log(filterToRemove, filterableName); 156 | 157 | let promise: Promise; 158 | let filterable: pbi.IFilterable; 159 | let filtersNode: IFiltersNode; 160 | 161 | if (!filterableName) { 162 | filterable = this.report; 163 | filtersNode = this.filtersNode; 164 | } 165 | else { 166 | let filteredPages = this.reportPages.filter(page => page.name === filterableName); 167 | if (filteredPages.length !== 1) { 168 | throw new Error(`Could not find filterable object matching name: ${filterableName}. There is likely a problem with how the filterableName is being assigned in event.`); 169 | } 170 | 171 | filterable = filteredPages[0]; 172 | 173 | let filteredNodes = this.filtersNode.nodes.filter(node => node.name === filteredPages[0].name); 174 | if (filteredNodes.length !== 1) { 175 | throw new Error(`Could not find node matching name: ${filteredPages[0].name}.`); 176 | } 177 | 178 | filtersNode = filteredNodes[0]; 179 | } 180 | 181 | return filterable.getFilters() 182 | .then(filters => { 183 | let index = -1; 184 | filters.some((filter, i) => { 185 | if (this.areFiltersEqual(filter, filterToRemove)) { 186 | index = i; 187 | return true; 188 | } 189 | }); 190 | 191 | if (index !== -1) { 192 | filters.splice(index, 1); 193 | return filterable.setFilters(filters) 194 | .then(() => { 195 | this.$scope.$apply(() => { 196 | filtersNode.filters = filters; 197 | }); 198 | }); 199 | } 200 | 201 | return Promise.reject(new Error('Could not find filter')); 202 | }); 203 | } 204 | 205 | removeReportFiltersClicked() { 206 | console.log('removeReportFilters'); 207 | this.report.removeFilters(); 208 | } 209 | 210 | removePageFiltersClicked(page: pbi.Page) { 211 | console.log('removePagefiltes', page); 212 | page.removeFilters(); 213 | } 214 | 215 | removeVisualFiltersClicked(visual: pbi.Visual) { 216 | console.log('removeVisualFilters', visual); 217 | // TODO: Need to return page name in order to properly reference visual 218 | visual.removeFilters(); 219 | } 220 | 221 | predefinedFilter1Clicked() { 222 | this.report.setFilters([Controller.predefinedFilter1.toJSON()]); 223 | } 224 | 225 | predefinedFilter2Clicked() { 226 | this.report.setFilters([Controller.predefinedFilter2.toJSON()]); 227 | } 228 | 229 | predefinedFIlter3Clicked() { 230 | this.report.page('ReportSection2').setFilters([Controller.predefinedFilter3.toJSON()]); 231 | } 232 | 233 | private areFiltersEqual( 234 | filterA: pbi.models.IFilter, 235 | filterB: pbi.models.IFilter 236 | ) { 237 | let filterAType = pbi.models.getFilterType(filterA); 238 | let filterATarget: any = filterA.target; 239 | let advancedFilterA: pbi.models.IAdvancedFilter; 240 | let basicFilterA: pbi.models.IBasicFilter; 241 | let filterBType = pbi.models.getFilterType(filterB); 242 | let filterBTarget: any = filterB.target; 243 | let advancedFilterB: pbi.models.IAdvancedFilter; 244 | let basicFilterB: pbi.models.IBasicFilter; 245 | 246 | if (filterAType === pbi.models.FilterType.Advanced) { 247 | advancedFilterA = filterA; 248 | } 249 | else if (filterAType === pbi.models.FilterType.Basic) { 250 | basicFilterA = filterA; 251 | } 252 | 253 | if (filterBType === pbi.models.FilterType.Advanced) { 254 | advancedFilterB = filterB; 255 | } 256 | else if (filterBType === pbi.models.FilterType.Basic) { 257 | basicFilterB = filterB; 258 | } 259 | 260 | const areTargetsEqual = filterATarget.table === filterBTarget.table 261 | && filterATarget.column === filterBTarget.column 262 | && filterATarget.hierarchy === filterBTarget.hierarchy 263 | && filterATarget.hierarchyLevel === filterBTarget.hierarchyLevel 264 | && filterATarget.measure === filterBTarget.measure 265 | ; 266 | 267 | if (!areTargetsEqual) { 268 | return false; 269 | } 270 | 271 | if (advancedFilterA && advancedFilterB) { 272 | return advancedFilterA.logicalOperator === advancedFilterB.logicalOperator 273 | && advancedFilterA.conditions.every(condition => { 274 | return advancedFilterB.conditions.some(conditionB => { 275 | return condition.operator === conditionB.operator 276 | && condition.value === conditionB.value 277 | ; 278 | }); 279 | }) 280 | ; 281 | } 282 | else if (basicFilterA && basicFilterB) { 283 | return basicFilterA.operator === basicFilterB.operator 284 | && basicFilterA.values.every(value => { 285 | return basicFilterB.values.some(valueB => valueB === value) 286 | }); 287 | } 288 | 289 | return false; 290 | } 291 | } -------------------------------------------------------------------------------- /app/scenario4/module.ts: -------------------------------------------------------------------------------- 1 | import controller from './controller'; 2 | import route from './route'; 3 | 4 | export { controller, route }; -------------------------------------------------------------------------------- /app/scenario4/route.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularuirouter from 'angular-ui-router'; 3 | import { ReportsService } from '../services/reports'; 4 | 5 | const modelResolver = (ReportsService: ReportsService): ng.IPromise => { 6 | return ReportsService.findById('c52af8ab-0468-4165-92af-dc39858d66ad'); 7 | }; 8 | modelResolver["$inject"] = ['ReportsService']; 9 | 10 | const state: angularuirouter.IState = { 11 | name: "application.scenario4", 12 | parent: "application", 13 | url: "^/scenario4", 14 | views: { 15 | 'main': { 16 | templateUrl: "/app/scenario4/template.html", 17 | controller: "Scenario4Controller", 18 | controllerAs: "vm" 19 | } 20 | }, 21 | resolve: { 22 | scenario4model: modelResolver 23 | } 24 | } 25 | 26 | export default state; -------------------------------------------------------------------------------- /app/scenario4/template.html: -------------------------------------------------------------------------------- 1 |

Custom Filter Pane

2 |

Filter pane is hidden in the embedded report and recreated by developer to allow custom branding or special preconfigured filters.

3 | 4 |
5 |
6 |
7 |
8 | 9 |
10 |
11 |
12 |
13 |

Report

14 |
15 |
16 |

Page

17 |
18 |
19 |

Visual

20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 | 36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 | 49 |
50 | 51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 60 |

Store > Name Contains Direct

61 | 62 |

Store > Name contains 'Wash' or contains 'Park'

63 |
64 |
65 | 66 |

Store > Name contains 'Wash' or contains 'Park' (Page: District Monthly Sales)

67 |
68 |
69 | N/A 70 |
71 |
72 |
73 |
74 | 82 | 83 |
84 |
85 | -------------------------------------------------------------------------------- /app/scenario5/controller.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularPbi from 'angular-powerbi'; 3 | 4 | export default class controller { 5 | $q: ng.IQService; 6 | $scope: ng.IScope; 7 | 8 | embedConfiguration: pbi.IEmbedConfiguration; 9 | report: pbi.Report; 10 | title: string; 11 | 12 | static $inject = [ 13 | '$q', 14 | '$scope', 15 | 'scenario5model' 16 | ]; 17 | 18 | constructor( 19 | $q: ng.IQService, 20 | $scope: ng.IScope, 21 | embedConfiguration: pbi.IEmbedConfiguration 22 | ) { 23 | this.$q = $q; 24 | this.$scope = $scope; 25 | 26 | this.title = 'Scenario 5'; 27 | const filter = new pbi.models.AdvancedFilter({ 28 | table: "Store", 29 | column: "Name" 30 | }, "Or", { 31 | operator: "Contains", 32 | value: "Wash" 33 | }, 34 | { 35 | operator: "Contains", 36 | value: "Park" 37 | }); 38 | 39 | this.embedConfiguration = angular.extend(embedConfiguration, { 40 | settings: { 41 | filterPaneEnabled: false, 42 | navContentPaneEnabled: true 43 | }, 44 | pageName: 'ReportSection2', 45 | filter 46 | }); 47 | } 48 | } -------------------------------------------------------------------------------- /app/scenario5/module.ts: -------------------------------------------------------------------------------- 1 | import controller from './controller'; 2 | import route from './route'; 3 | 4 | export { controller, route }; -------------------------------------------------------------------------------- /app/scenario5/route.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularuirouter from 'angular-ui-router'; 3 | import { ReportsService } from '../services/reports'; 4 | 5 | const modelResolver = (ReportsService: ReportsService): ng.IPromise => { 6 | return ReportsService.findById('c52af8ab-0468-4165-92af-dc39858d66ad'); 7 | }; 8 | modelResolver["$inject"] = ['ReportsService']; 9 | 10 | const state: angularuirouter.IState = { 11 | name: "application.scenario5", 12 | parent: "application", 13 | url: "^/scenario5", 14 | views: { 15 | 'main': { 16 | templateUrl: "/app/scenario5/template.html", 17 | controller: "Scenario5Controller", 18 | controllerAs: "vm" 19 | } 20 | }, 21 | resolve: { 22 | scenario5model: modelResolver 23 | } 24 | } 25 | 26 | export default state; -------------------------------------------------------------------------------- /app/scenario5/template.html: -------------------------------------------------------------------------------- 1 |

Default Page and/or Default Filter

2 |

Load a report at a specified page and/or report level filter.

3 | 4 | -------------------------------------------------------------------------------- /app/scenario6/controller.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularPbi from 'angular-powerbi'; 3 | 4 | export default class controller { 5 | $q: ng.IQService; 6 | $scope: ng.IScope; 7 | 8 | embedConfiguration: pbi.IEmbedConfiguration; 9 | report: pbi.Report; 10 | title: string; 11 | filterPaneEnabled: boolean = false; 12 | navContentPaneEnabled: boolean = false; 13 | 14 | static $inject = [ 15 | '$q', 16 | '$scope', 17 | 'scenario6model' 18 | ]; 19 | 20 | constructor( 21 | $q: ng.IQService, 22 | $scope: ng.IScope, 23 | embedConfiguration: pbi.IEmbedConfiguration 24 | ) { 25 | this.$q = $q; 26 | this.$scope = $scope; 27 | 28 | this.title = 'Scenario 6'; 29 | this.embedConfiguration = angular.extend(embedConfiguration, { 30 | settings: { 31 | filterPaneEnabled: this.filterPaneEnabled, 32 | navContentPaneEnabled: this.navContentPaneEnabled 33 | } 34 | }); 35 | } 36 | 37 | onEmbedded(report: pbi.Report) { 38 | console.log('embedded settings report'); 39 | this.report = report; 40 | } 41 | 42 | toggleFilterPaneClicked() { 43 | console.log('toggleFilterPaneClicked'); 44 | this.filterPaneEnabled = !this.filterPaneEnabled; 45 | this.report.updateSettings({ 46 | filterPaneEnabled: this.filterPaneEnabled 47 | }); 48 | } 49 | 50 | toggleNavContentPaneClicked() { 51 | console.log('toggleNavContentPaneClicked'); 52 | this.navContentPaneEnabled = !this.navContentPaneEnabled; 53 | this.report.updateSettings({ 54 | navContentPaneEnabled: this.navContentPaneEnabled 55 | }); 56 | } 57 | } -------------------------------------------------------------------------------- /app/scenario6/module.ts: -------------------------------------------------------------------------------- 1 | import controller from './controller'; 2 | import route from './route'; 3 | 4 | export { controller, route }; -------------------------------------------------------------------------------- /app/scenario6/route.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | import * as angularuirouter from 'angular-ui-router'; 3 | import { ReportsService } from '../services/reports'; 4 | 5 | const modelResolver = (ReportsService: ReportsService): ng.IPromise => { 6 | return ReportsService.findById('c52af8ab-0468-4165-92af-dc39858d66ad'); 7 | }; 8 | modelResolver["$inject"] = ['ReportsService']; 9 | 10 | const state: angularuirouter.IState = { 11 | name: "application.scenario6", 12 | parent: "application", 13 | url: "^/scenario6", 14 | views: { 15 | 'main': { 16 | templateUrl: "/app/scenario6/template.html", 17 | controller: "Scenario6Controller", 18 | controllerAs: "vm" 19 | } 20 | }, 21 | resolve: { 22 | scenario6model: modelResolver 23 | } 24 | } 25 | 26 | export default state; -------------------------------------------------------------------------------- /app/scenario6/template.html: -------------------------------------------------------------------------------- 1 |

Update Settings

2 |

Change visibility of filter pane or page navigation dynamically

3 | 4 |
5 |
6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/services/reports.ts: -------------------------------------------------------------------------------- 1 | import * as pbi from 'powerbi-client'; 2 | 3 | export class ReportsService { 4 | $http: ng.IHttpService 5 | baseUrl: string; 6 | 7 | static $inject = [ 8 | '$http' 9 | ]; 10 | 11 | constructor($http: ng.IHttpService, baseUrl: string) { 12 | this.$http = $http; 13 | this.baseUrl = baseUrl; 14 | } 15 | 16 | findAll(): ng.IPromise { 17 | return this.$http.get(`${this.baseUrl}/api/reports`) 18 | .then(response => response.data) 19 | .then((reports: pbi.IEmbedConfiguration[]) => reports.map(this.normalizeReport)) 20 | ; 21 | } 22 | 23 | findById(id: string, dxt: boolean = false): ng.IPromise { 24 | const url = dxt ? `${this.baseUrl}/api/dxt/reports/${id}` : `${this.baseUrl}/api/reports/${id}`; 25 | 26 | return this.$http.get(url) 27 | .then(response => response.data) 28 | .then(this.normalizeReport) 29 | ; 30 | } 31 | 32 | findByName(search: string): ng.IPromise { 33 | return this.$http.get(`${this.baseUrl}/api/reports?query=${search}`) 34 | .then(response => response.data) 35 | .then((reports: pbi.IEmbedConfiguration[]) => reports.map(this.normalizeReport)) 36 | ; 37 | } 38 | 39 | private normalizeReport(report: pbi.IEmbedConfiguration) { 40 | report.type = "report"; 41 | return report; 42 | } 43 | } 44 | 45 | export default function ReportsServiceProvider() { 46 | var baseUrl = ''; 47 | 48 | return { 49 | setBaseUrl(url: string) { 50 | baseUrl = url; 51 | }, 52 | 53 | $get: ['$http', function ($http: ng.IHttpService) { 54 | return new ReportsService($http, baseUrl); 55 | }] 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /app/services/utilities.ts: -------------------------------------------------------------------------------- 1 | export default class Utilities { 2 | $timeout: ng.ITimeoutService; 3 | 4 | static $inject = [ 5 | '$timeout' 6 | ] 7 | 8 | constructor($timeout: ng.ITimeoutService) { 9 | this.$timeout = $timeout; 10 | } 11 | 12 | debounce(func: Function, wait: number): Function { 13 | let previousTimeoutPromise: ng.IPromise; 14 | 15 | return (...args: any[]) => { 16 | if (previousTimeoutPromise) { 17 | this.$timeout.cancel(previousTimeoutPromise); 18 | } 19 | 20 | previousTimeoutPromise = this.$timeout(() => func(...args), wait); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/styles/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 2em 0; 3 | } 4 | 5 | .powerbi-frame--dotted { 6 | border: 2px dashed #cecece; 7 | } 8 | .powerbi-frame { 9 | margin: 0 0 3rem 0; 10 | height: 600px; 11 | } 12 | .powerbi-frame iframe { 13 | border: none; 14 | } 15 | 16 | #reportslist { 17 | margin: 0 0 1em 0; 18 | } 19 | #reportslist li { 20 | margin: 1em 0; 21 | } 22 | .reportslistdescription { 23 | margin: 1em 0 0 0; 24 | font-weight: bold; 25 | } 26 | .report-name { 27 | display: inline-block; 28 | margin: 0 1em; 29 | font-weight: bold; 30 | } 31 | .checkbox { 32 | margin-left: 1em; 33 | } 34 | 35 | .powerbi-page-navigation { 36 | display: flex; 37 | margin-top: 2em; 38 | } 39 | .powerbi-page-navigation__pages { 40 | flex: 1; 41 | } 42 | 43 | .powerbi-page-navigation__pages button { 44 | margin-left: 1em; 45 | } 46 | .powerbi-page-navigation__pages button.btn-success.active { 47 | background-color: red; 48 | } 49 | .powerbi-page-navigation__cycle { 50 | margin-right: 1em; 51 | } 52 | .powerbi-page-navigation__cycle.btn-warning.active { 53 | background-color: red; 54 | } 55 | 56 | .filters > * + * { 57 | margin-top: 1em; 58 | } 59 | .filter { 60 | padding: 0.5em; 61 | border: 1px solid #ddd; 62 | border-radius: 4px; 63 | } 64 | .filter__remove { 65 | float: right; 66 | } 67 | .filter__text { 68 | margin-right: 3em; 69 | word-break: break-all; 70 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 71 | font-size: 90%; 72 | } -------------------------------------------------------------------------------- /gulp/config.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | const config = { 3 | allJs: ['app/**/*.js'], 4 | templates: ['app/**/*template.html'], 5 | htmlPage: 'app/index.html', 6 | distFolder: 'dist/', 7 | 8 | angularTemplateCache: { 9 | templateHeader: 'export default function($templateCache) {', 10 | templateBody: '$templateCache.put("<%= url %>","<%= contents %>");', 11 | templateFooter: '};' 12 | } 13 | }; 14 | 15 | return config; 16 | }; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp-help')(require('gulp')), 2 | $ = require('gulp-load-plugins')({ lazy: true }), 3 | config = require('./gulp/config')(), 4 | del = require('del'), 5 | merge2 = require('merge2'), 6 | moment = require('moment'), 7 | runSequence = require('run-sequence'), 8 | webpack = require('webpack-stream'), 9 | webpackConfig = require('./webpack.config') 10 | ; 11 | 12 | gulp.task('build', function (done) { 13 | runSequence( 14 | 'clean:dist', 15 | 'compile:src', 16 | ['copycss', 'vendor'], 17 | ['templates', 'replace'], 18 | done 19 | ); 20 | }); 21 | 22 | gulp.task('ghpages', 'Deploy application to gh-pages branch', function () { 23 | return gulp.src(['./dist/**/*']) 24 | .pipe($.ghPages({ 25 | force: true, 26 | message: 'Update ' + moment().format('LLL') 27 | })); 28 | }); 29 | 30 | gulp.task('compile:watch', 'Watch sources', function () { 31 | gulp.watch(['./app/**/*.ts', './app/**/*.html'], ['compile:src', 'templates', 'replace']); 32 | }); 33 | 34 | gulp.task('clean:dist', function() { 35 | // You can use multiple globbing patterns as you would with `gulp.src` 36 | return del(['dist']); 37 | }); 38 | 39 | gulp.task('compile:src', 'Compile typescript for library', function() { 40 | return gulp.src(['./app/**/*.ts']) 41 | .pipe($.plumber({ 42 | errorHandler: function (error) { 43 | console.log(error); 44 | this.emit('end'); 45 | } 46 | })) 47 | .pipe(webpack(webpackConfig)) 48 | .pipe(gulp.dest('./dist')); 49 | }); 50 | 51 | gulp.task('templates', function () { 52 | return gulp.src(config.templates) 53 | .pipe($.plumber({ 54 | errorHandler: function (error) { 55 | console.log(error); 56 | this.emit('end'); 57 | } 58 | })) 59 | .pipe($.minifyHtml({ 60 | empty: true 61 | })) 62 | .pipe($.angularTemplatecache('app.templates.js', { 63 | module: 'app', 64 | root: '/app' 65 | })) 66 | .pipe(gulp.dest(config.distFolder)) 67 | ; 68 | }); 69 | 70 | gulp.task('copycss', function () { 71 | return gulp.src([ 72 | './node_modules/bootstrap/dist/css/bootstrap.css', 73 | './app/styles/app.css' 74 | ]) 75 | .pipe(gulp.dest(config.distFolder)) 76 | ; 77 | }); 78 | 79 | gulp.task('vendor', function () { 80 | return gulp.src([ 81 | './node_modules/angular/angular.js', 82 | './node_modules/angular-ui-router/release/angular-ui-router.js', 83 | './node_modules/powerbi-client/dist/powerbi.js', 84 | './node_modules/angular-powerbi/dist/angular-powerbi.js' 85 | ]) 86 | .pipe($.concat('vendor.js')) 87 | .pipe(gulp.dest(config.distFolder)) 88 | ; 89 | }); 90 | 91 | gulp.task('replace', function () { 92 | return gulp.src(config.htmlPage) 93 | .pipe($.htmlReplace({ 94 | 'css': ['bootstrap.css', 'app.css'], 95 | 'js': ['vendor.js', 'app.js', 'app.templates.js'] 96 | })) 97 | .pipe(gulp.dest(config.distFolder)) 98 | ; 99 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "powerbi-angular-client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/index.html", 6 | "scripts": { 7 | "build": "gulp build", 8 | "start": "gulp build && http-server", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "prepublish": "typings install", 11 | "gulp": "gulp", 12 | "typings": "typings" 13 | }, 14 | "homepage": "http://azure-samples.github.io/powerbi-angular-client", 15 | "repository": "https://github.com/Azure-Samples/powerbi-angular-client", 16 | "author": "Microsoft Power BI Team", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "angular-templatecache": "0.0.1", 20 | "del": "^2.2.2", 21 | "gulp": "^3.9.1", 22 | "gulp-angular-templatecache": "^1.8.0", 23 | "gulp-concat": "^2.6.0", 24 | "gulp-debug": "^2.1.2", 25 | "gulp-gh-pages": "^0.5.4", 26 | "gulp-help": "^1.6.1", 27 | "gulp-html-replace": "^1.5.5", 28 | "gulp-load-plugins": "^1.2.4", 29 | "gulp-minify-html": "^1.0.6", 30 | "gulp-plumber": "^1.1.0", 31 | "gulp-rename": "^1.2.2", 32 | "gulp-typescript": "^2.13.0", 33 | "http-server": "^0.9.0", 34 | "merge2": "^1.0.2", 35 | "moment": "^2.14.1", 36 | "run-sequence": "^1.1.5", 37 | "ts-loader": "^0.8.2", 38 | "typings": "^1.3.2", 39 | "webpack-stream": "^3.2.0" 40 | }, 41 | "dependencies": { 42 | "angular-powerbi": "^1.0.0", 43 | "angular-ui-router": "^0.3.1", 44 | "bootstrap": "^3.3.6" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "moduleResolution": "node", 5 | "noImplicitAny": true, 6 | "sourceMap": true 7 | }, 8 | "exclude": [ 9 | "node_modules", 10 | "typings/index.d.ts" 11 | ] 12 | } -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "powerbi-angular-client", 3 | "dependencies": {}, 4 | "globalDependencies": { 5 | "angular": "registry:dt/angular#1.5.0+20160619025852", 6 | "angular-ui-router": "registry:dt/angular-ui-router#1.1.5+20160521151413", 7 | "es6-promise": "registry:dt/es6-promise#0.0.0+20160614011821" 8 | }, 9 | "globalDevDependencies": { 10 | "jquery": "registry:dt/jquery#1.10.0+20160620094458" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var package = require('./package.json'); 2 | 3 | module.exports = { 4 | entry: { 5 | 'app': './app/app.ts' 6 | }, 7 | output: { 8 | path: __dirname + "/webpack", 9 | filename: '[name].js' 10 | }, 11 | externals: { 12 | 'angular': 'angular', 13 | 'powerbi-client': "window['powerbi-client']" 14 | }, 15 | devtool: 'source-map', 16 | resolve: { 17 | extensions: ['', '.webpack.js', '.web.js', '.ts', '.js'] 18 | }, 19 | module: { 20 | loaders: [ 21 | { test: /\.ts$/, loader: 'ts-loader' } 22 | ] 23 | } 24 | } --------------------------------------------------------------------------------