diff --git a/package.json b/package.json index 682f55f..7a379a2 100644 --- a/package.json +++ b/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", diff --git a/src/models.ts b/src/models.ts index 5f2dbca..9ac2993 100644 --- a/src/models.ts +++ b/src/models.ts @@ -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; @@ -662,6 +664,7 @@ export interface IReportLoadConfiguration { settings?: ISettings; pageName?: string; filters?: ReportLevelFilters[]; + slicers?: ISlicer[]; permissions?: Permissions; viewMode?: ViewMode; tokenType?: TokenType; @@ -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 = 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) { @@ -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; diff --git a/src/validators/core/validator.ts b/src/validators/core/validator.ts index d5b9b98..51f17df 100644 --- a/src/validators/core/validator.ts +++ b/src/validators/core/validator.ts @@ -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; @@ -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(), @@ -96,4 +100,5 @@ export const Validators = { topNFilterValidator: new TopNFilterValidator(), viewModeValidator: new EnumValidator([0, 1]), visualLayoutValidator: new VisualLayoutValidator(), + visualSelectorValidator: new VisualSelectorValidator(), }; diff --git a/src/validators/models/selectorsValidator.ts b/src/validators/models/selectorsValidator.ts new file mode 100644 index 0000000..6dfa3de --- /dev/null +++ b/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); + } +} diff --git a/src/validators/models/slicersValidator.ts b/src/validators/models/slicersValidator.ts new file mode 100644 index 0000000..d3fea3f --- /dev/null +++ b/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); + } +} diff --git a/test/models.spec.ts b/test/models.spec.ts index d709316..30a1aeb 100644 --- a/test/models.spec.ts +++ b/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) { @@ -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 () {