Skip to content

Commit

Permalink
Merge remote-tracking branch 'powerbi/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
ali-hamud committed Jun 28, 2018
2 parents 91fa0c6 + ae31fbc commit 0adcb94
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "powerbi-models",
"version": "1.0.6",
"version": "1.0.7",
"description": "Contains JavaScript & TypeScript object models for Microsoft Power BI JavaScript SDK. For each model there is a TypeScript interface, and a validation function to ensure and object is valid.",
"main": "dist/models.js",
"typings": "dist/models.d.ts",
Expand Down
72 changes: 72 additions & 0 deletions src/models.ts
Expand Up @@ -282,6 +282,8 @@ export type BasicFilterOperators = "In" | "NotIn" | "All";
export type AdvancedFilterLogicalOperators = "And" | "Or";
export type AdvancedFilterConditionOperators = "None" | "LessThan" | "LessThanOrEqual" | "GreaterThan" | "GreaterThanOrEqual" | "Contains" | "DoesNotContain" | "StartsWith" | "DoesNotStartWith" | "Is" | "IsNot" | "IsBlank" | "IsNotBlank";

export type SlicerSelector = IVisualSelector;

export interface IAdvancedFilterCondition {
value: (string | number | boolean | Date);
operator: AdvancedFilterConditionOperators;
Expand Down Expand Up @@ -662,6 +664,7 @@ export interface IReportLoadConfiguration {
settings?: ISettings;
pageName?: string;
filters?: ReportLevelFilters[];
slicers?: ISlicer[];
permissions?: Permissions;
viewMode?: ViewMode;
tokenType?: TokenType;
Expand Down Expand Up @@ -778,6 +781,60 @@ export interface IExportDataResult {
data: string;
}

/*
* Selectors
*/
export interface ISelector {
$schema: string;
}

export interface IVisualSelector extends ISelector {
visualName: string;
}

export abstract class Selector implements ISelector {
public $schema: string;

constructor(schema: string) {
this.$schema = schema;
}

toJSON(): ISelector {
return {
$schema: this.$schema
};
};
}

export class VisualSelector extends Selector implements IVisualSelector {
static schemaUrl: string = "http://powerbi.com/product/schema#visualSelector";
public visualName: string;

constructor(visualName: string) {
super(VisualSelector.schemaUrl);
this.visualName = visualName;
}

toJSON(): IVisualSelector {
const selector = <IVisualSelector>super.toJSON();

selector.visualName = this.visualName;
return selector;
}
}

/*
* Slicers
*/
export interface ISlicer {
selector: SlicerSelector;
state: ISlicerState;
}

export interface ISlicerState {
filters: ISlicerFilter[];
}

function normalizeError(error: any): IError {
let message = error.message;
if (!message) {
Expand All @@ -788,6 +845,21 @@ function normalizeError(error: any): IError {
};
}

export function validateVisualSelector(input: any): IError[] {
let errors: any[] = Validators.visualSelectorValidator.validate(input);
return errors ? errors.map(normalizeError) : undefined;
}

export function validateSlicer(input: any): IError[] {
let errors: any[] = Validators.slicerValidator.validate(input);
return errors ? errors.map(normalizeError) : undefined;
}

export function validateSlicerState(input: any): IError[] {
let errors: any[] = Validators.slicerStateValidator.validate(input);
return errors ? errors.map(normalizeError) : undefined;
}

export function validatePlayBookmarkRequest(input: any): IError[] {
let errors: any[] = Validators.playBookmarkRequestValidator.validate(input);
return errors ? errors.map(normalizeError) : undefined;
Expand Down
5 changes: 5 additions & 0 deletions src/validators/core/validator.ts
Expand Up @@ -15,6 +15,8 @@ import { SaveAsParametersValidator } from '../models/saveAsParametersValidator';
import { MapValidator } from './mapValidator';
import { CustomLayoutValidator, VisualLayoutValidator, PageLayoutValidator, DisplayStateValidator } from '../models/layoutValidator';
import { ExportDataRequestValidator } from '../models/exportDataValidator';
import { VisualSelectorValidator } from '../models/selectorsValidator';
import { SlicerValidator, SlicerStateValidator } from '../models/slicersValidator';

export interface IValidationError {
path?: string;
Expand Down Expand Up @@ -88,6 +90,8 @@ export const Validators = {
reportLoadValidator: new ReportLoadValidator(),
saveAsParametersValidator: new SaveAsParametersValidator(),
settingsValidator: new SettingsValidator(),
slicerValidator: new SlicerValidator(),
slicerStateValidator: new SlicerStateValidator(),
stringArrayValidator: new StringArrayValidator(),
stringValidator: new StringValidator(),
tileLoadValidator: new TileLoadValidator(),
Expand All @@ -96,4 +100,5 @@ export const Validators = {
topNFilterValidator: new TopNFilterValidator(),
viewModeValidator: new EnumValidator([0, 1]),
visualLayoutValidator: new VisualLayoutValidator(),
visualSelectorValidator: new VisualSelectorValidator(),
};
26 changes: 26 additions & 0 deletions src/validators/models/selectorsValidator.ts
@@ -0,0 +1,26 @@
import { IValidationError, Validators } from '../core/validator';
import { MultipleFieldsValidator, IFieldValidatorsPair } from '../core/multipleFieldsValidator';
import { ObjectValidator } from '../core/typeValidator';

export class VisualSelectorValidator extends ObjectValidator {
public validate(input: any, path?: string, field?: string): IValidationError[] {
if (input == null) {
return null;
}

const errors = super.validate(input, path, field);
if (errors) {
return errors;
}

const fields: IFieldValidatorsPair[] = [
{
field: "visualName",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
}
];

const multipleFieldsValidator = new MultipleFieldsValidator(fields);
return multipleFieldsValidator.validate(input, path, field);
}
}
53 changes: 53 additions & 0 deletions src/validators/models/slicersValidator.ts
@@ -0,0 +1,53 @@
import { IValidationError, Validators } from '../core/validator';
import { MultipleFieldsValidator, IFieldValidatorsPair } from '../core/multipleFieldsValidator';
import { ObjectValidator } from '../core/typeValidator';

export class SlicerValidator extends ObjectValidator {
public validate(input: any, path?: string, field?: string): IValidationError[] {
if (input == null) {
return null;
}

const errors = super.validate(input, path, field);
if (errors) {
return errors;
}

const fields: IFieldValidatorsPair[] = [
{
field: "selector",
validators: [Validators.fieldRequiredValidator, Validators.visualSelectorValidator]
},
{
field: "state",
validators: [Validators.fieldRequiredValidator, Validators.slicerStateValidator]
}
];

const multipleFieldsValidator = new MultipleFieldsValidator(fields);
return multipleFieldsValidator.validate(input, path, field);
}
}

export class SlicerStateValidator extends ObjectValidator {
public validate(input: any, path?: string, field?: string): IValidationError[] {
if (input == null) {
return null;
}

const errors = super.validate(input, path, field);
if (errors) {
return errors;
}

const fields: IFieldValidatorsPair[] = [
{
field: "filters",
validators: [Validators.filtersArrayValidator]
}
];

const multipleFieldsValidator = new MultipleFieldsValidator(fields);
return multipleFieldsValidator.validate(input, path, field);
}
}
89 changes: 89 additions & 0 deletions test/models.spec.ts
@@ -1,4 +1,5 @@
import * as models from '../src/models';
import { IFilter } from '../src/models';

describe('Unit | Models', function () {
function testForExpectedMessage(errors: models.IError[], message: string) {
Expand Down Expand Up @@ -1633,6 +1634,94 @@ describe('Unit | Models', function () {
expect(errors).toBeUndefined();
});
});

describe('validateSlicers', function() {
const selectorRequiredMessage = "selector is required";
const stateRequiredMessage = "state is required";
const selectorInvalidTypeMessage = "selector must be an object";
const stateInvalidTypeMessage = "state must be an object";
const filters: IFilter[] = [];

it(`should return undefined if selector and state are valid`, function () {
// Arrange
const testData = {
selector: {
visualName: 'fakeId',
},
state: {
filters: filters
}
};

// Act
const errors = models.validateSlicer(testData);

// Assert
expect(errors).toBeUndefined();
});

it(`should return errors with one containing message '${selectorRequiredMessage}' if datasetIds field is not an array of strings`, function () {
// Arrange
const testData = {
state: {
filters: filters
}
};

// Act
const errors = models.validateSlicer(testData);

// Assert
testForExpectedMessage(errors, selectorRequiredMessage);
});

it(`should return errors with one containing message '${stateRequiredMessage}' if datasetIds field is not an array of strings`, function () {
// Arrange
const testData = {
selector: {
visualName: 'fakeId',
}
};

// Act
const errors = models.validateSlicer(testData);

// Assert
testForExpectedMessage(errors, stateRequiredMessage);
});

it(`should return errors with one containing message '${selectorInvalidTypeMessage}' if datasetIds field is not an array of strings`, function () {
// Arrange
const testData = {
selector: 11,
state: {
filters: filters
}
};

// Act
const errors = models.validateSlicer(testData);

// Assert
testForExpectedMessage(errors, selectorInvalidTypeMessage);
});

it(`should return errors with one containing message '${stateInvalidTypeMessage}' if datasetIds field is not an array of strings`, function () {
// Arrange
const testData = {
selector: {
visualName: 'fakeId',
},
state: 11
};

// Act
const errors = models.validateSlicer(testData);

// Assert
testForExpectedMessage(errors, stateInvalidTypeMessage);
});
});
});

describe("Unit | Filters", function () {
Expand Down

0 comments on commit 0adcb94

Please sign in to comment.