diff --git a/package.json b/package.json index 790e3bd..48e8ec2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "powerbi-models", - "version": "0.10.2", + "version": "0.10.3", "description": "Contains JavaScript & TypeScript object models for Microsoft Power BI JavaScript SDK. For each model there is a TypeScript interface, a json schema definitions, 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 ebaf215..9b6207e 100644 --- a/src/models.ts +++ b/src/models.ts @@ -126,6 +126,10 @@ export interface IFilterColumnTarget extends IBaseFilterTarget { column: string; } +export interface IFilterKeyColumnsTarget extends IFilterColumnTarget { + keys: string[]; +} + export interface IFilterHierarchyTarget extends IBaseFilterTarget { hierarchy: string; hierarchyLevel: string; @@ -147,6 +151,11 @@ export interface IBasicFilter extends IFilter { values: (string | number | boolean)[]; } +export interface IBasicFilterWithKeys extends IBasicFilter { + target: IFilterKeyColumnsTarget; + keyValues: (string | number | boolean)[][]; +} + 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"; @@ -167,6 +176,14 @@ export enum FilterType { Unknown } +export function isFilterKeyColumnsTarget(target: IFilterTarget): boolean { + return isColumn(target) && !!(target).keys; +} + +export function isBasicFilterWithKeys(filter: IFilter): boolean { + return getFilterType(filter) === FilterType.Basic && !!(filter).keyValues; +} + export function getFilterType(filter: IFilter): FilterType { const basicFilter = filter as IBasicFilter; const advancedFilter = filter as IAdvancedFilter; @@ -222,6 +239,7 @@ export class BasicFilter extends Filter { static schemaUrl: string = "http://powerbi.com/product/schema#basic"; operator: BasicFilterOperators; values: (string | number | boolean)[]; + keyValues: (string | number | boolean)[][]; constructor( target: IFilterTarget, @@ -233,7 +251,7 @@ export class BasicFilter extends Filter { this.schemaUrl = BasicFilter.schemaUrl; if (values.length === 0 && operator !== "All") { - throw new Error(`values must be a non-empty array unless your operator is "All". You passed: ${values}`); + throw new Error(`values must be a non-empty array unless your operator is "All".`); } /** @@ -259,6 +277,47 @@ export class BasicFilter extends Filter { } } +export class BasicFilterWithKeys extends BasicFilter { + keyValues: (string | number | boolean)[][]; + target: IFilterKeyColumnsTarget; + + constructor( + target: IFilterKeyColumnsTarget, + operator: BasicFilterOperators, + values: ((string | number | boolean) | (string | number | boolean)[]), + keyValues: (string | number | boolean)[][] + ) { + super(target, operator, values); + this.keyValues = keyValues; + this.target = target; + let numberOfKeys = target.keys ? target.keys.length : 0; + + if (numberOfKeys > 0 && !keyValues) { + throw new Error(`You shold pass the values to be filtered for each key. You passed: no values and ${numberOfKeys} keys`); + } + + if (numberOfKeys === 0 && keyValues && keyValues.length > 0) { + throw new Error(`You passed key values but your target object doesn't contain the keys to be filtered`); + } + + for (let i = 0 ; i < this.keyValues.length ; i++) { + if (this.keyValues[i] ) { + let lengthOfArray = this.keyValues[i].length; + if (lengthOfArray !== numberOfKeys) { + throw new Error(`Each tuple of key values should contain a value for each of the keys. You passed: ${lengthOfArray} values and ${numberOfKeys} keys`); + } + } + + } + } + + toJSON(): IBasicFilter { + const filter = super.toJSON(); + filter.keyValues = this.keyValues; + return filter; + } +} + export class AdvancedFilter extends Filter { static schemaUrl: string = "http://powerbi.com/product/schema#advanced"; diff --git a/test/models.spec.ts b/test/models.spec.ts index 69ab540..7de0722 100644 --- a/test/models.spec.ts +++ b/test/models.spec.ts @@ -512,6 +512,30 @@ describe("Unit | Filters", function () { expect(basicFilter.values).toEqual(values); }); + it("should accept values as an array of tuples", function () { + // Arrange + const values = [1, 2]; + const keyValues = [[1, 2], [3,4]]; + + // Act + const basicFilter = new models.BasicFilterWithKeys({ table: "t", column: "c" , keys: ["1", "2"]}, "In", values, keyValues); + + // Assert + expect(basicFilter.values).toEqual(values); + }); + + it("should throw an exception when values are an array of tuples, but tuples length is different than keys length", function () { + // Arrange + const values = [1, 2]; + const keyValues = [[1, 2], [3,4]]; + + // Act + const attemptToCreateFilter = () => { + return new models.BasicFilterWithKeys({ table: "t", column: "c" , keys: ["1"]}, "In", values, keyValues); + }; + expect(attemptToCreateFilter).toThrowError(); + }); + it("should return valid json format when toJSON is called", function () { // Arrange const expectedFilter: models.IBasicFilter = { @@ -679,11 +703,12 @@ describe("Unit | Filters", function () { }); }); - describe('determine filter type', function () { + describe('determine types', function () { it('getFilterType should return type of filter given a filter object', function () { // Arrange const testData = { basicFilter: new models.BasicFilter({ table: "a", column: "b" }, "In", ["x", "y"]), + basicFilterWithKeys: new models.BasicFilterWithKeys({ table: "a", column: "b", keys: ["1", "2"] }, "In", ["x1", 1], [["x1", 1], ["y2",2]]), advancedFilter: new models.AdvancedFilter({ table: "a", column: "b" }, "And", { operator: "Contains", value: "x" }, { operator: "Contains", value: "x" } @@ -695,8 +720,31 @@ describe("Unit | Filters", function () { // Assert expect(models.getFilterType(testData.basicFilter.toJSON())).toBe(models.FilterType.Basic); + expect(models.getFilterType(testData.basicFilterWithKeys.toJSON())).toBe(models.FilterType.Basic); expect(models.getFilterType(testData.advancedFilter.toJSON())).toBe(models.FilterType.Advanced); expect(models.getFilterType(testData.nonFilter)).toBe(models.FilterType.Unknown); }); + + it('isFilterKeyColumnsTarget should return the correct response', function () { + // Arrange + let filterKeyColumnsTarget = { table: "a", column: "b", keys: ["key1"] }; + let filterColumnTarget = { table: "a", column: "b"}; + + // Assert + expect(models.isFilterKeyColumnsTarget(filterKeyColumnsTarget)).toBeTruthy(); + expect(models.isFilterKeyColumnsTarget(filterColumnTarget)).toBeFalsy(); + }); + + it('isBasicFilterWithKeys should return the correct response', function () { + // Arrange + const testData = { + basicFilter: new models.BasicFilter({ table: "a", column: "b" }, "In", ["x", "y"]), + basicFilterWithKeys: new models.BasicFilterWithKeys({ table: "a", column: "b", keys: ["1", "2"] }, "In", ["x1", 1], [["x1", 1], ["y2",2]]), + }; + + // Assert + expect(models.isBasicFilterWithKeys(testData.basicFilter.toJSON())).toBeFalsy(); + expect(models.isBasicFilterWithKeys(testData.basicFilterWithKeys.toJSON())).toBeTruthy(); + }); }); });