├── .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 | [](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 |
117 |
118 |
119 |
Applied Filters
120 |
121 | Refresh
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 |
< Prev
3 |
4 | {{page.displayName}}
5 | No pages loaded
6 |
7 |
Disable Enable Cycle
8 |
Next >
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 |
20 |
21 | Total Results: {{vm.reports.length}}
22 |
23 |
24 | {{report.name}}
25 | Embed!
26 |
27 |
28 |
29 | Reset
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 |
11 |
12 |
13 |
Report
14 |
15 |
16 |
Page
17 |
18 |
19 |
Visual
20 |
21 |
22 |
54 |
55 |
56 |
57 |
58 |
59 |
Predefined Basic Report Filter
60 |
Store > Name Contains Direct
61 |
Predefined Advanced Report Filter
62 |
Store > Name contains 'Wash' or contains 'Park'
63 |
64 |
65 |
Predefined Advanced Page Filter
66 |
Store > Name contains 'Wash' or contains 'Park' (Page: District Monthly Sales)
67 |
68 |
69 | N/A
70 |
71 |
72 |
73 |
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 |
6 |
7 |
8 | Toggle Filter Pane
9 | Toggle Page Navigation
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------