diff --git a/src/models.ts b/src/models.ts index a17e428..f870b52 100644 --- a/src/models.ts +++ b/src/models.ts @@ -124,7 +124,10 @@ export interface IBaseFilterTarget { export interface IFilterColumnTarget extends IBaseFilterTarget { column: string; - keys?: string[]; +} + +export interface IFilterKeyColumnsTarget extends IFilterColumnTarget { + keys: string[]; } export interface IFilterHierarchyTarget extends IBaseFilterTarget { @@ -145,7 +148,12 @@ export interface IFilter { export interface IBasicFilter extends IFilter { operator: BasicFilterOperators; - values: (string | number | boolean)[] | (string | number | boolean)[][]; + values: (string | number | boolean)[]; +} + +export interface IBasicFilterWithKeys extends IBasicFilter { + target: IFilterKeyColumnsTarget; + keyValues: (string | number | boolean)[][]; } export type BasicFilterOperators = "In" | "NotIn" | "All"; @@ -222,19 +230,20 @@ export abstract class Filter { export class BasicFilter extends Filter { static schemaUrl: string = "http://powerbi.com/product/schema#basic"; operator: BasicFilterOperators; - values: (string | number | boolean)[] | (string | number | boolean)[][]; + values: (string | number | boolean)[]; + keyValues: (string | number | boolean)[][]; constructor( target: IFilterTarget, operator: BasicFilterOperators, - ...values: ((string | number | boolean) | (string | number | boolean)[] | (string | number | boolean)[][])[] + ...values: ((string | number | boolean) | (string | number | boolean)[])[] ) { super(target); this.operator = operator; 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".`); } /** @@ -243,21 +252,10 @@ export class BasicFilter extends Filter { * new BasicFilter('a', 'b', [1,2]); */ if (Array.isArray(values[0])) { - this.values = <(string | number | boolean)[] | (string | number | boolean)[][]>values[0]; + this.values = <(string | number | boolean)[]>values[0]; } else { - this.values = <(string | number | boolean)[] | (string | number | boolean)[][]>values; - } - let numberOfKeys = (target).keys ? (target).keys.length : 0; - - for (let i = 0 ; i < this.values.length ; i++) { - if (this.values[i] && Array.isArray(this.values[i])) { - let lengthOfArray = (<(string | number | boolean)[]>this.values[i]).length; - if (lengthOfArray !== numberOfKeys) { - throw new Error(`Each tuple of values should contain a value for each of the key columns. You passed: ${lengthOfArray} values and ${numberOfKeys} key columns`); - } - } - + this.values = <(string | number | boolean)[]>values; } } @@ -271,6 +269,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 && !keyValues) { + throw new Error(`You shold pass the values to be filtered for each key. You passed: no values and ${numberOfKeys} keys`); + } + + if (!numberOfKeys && keyValues && keyValues.length > 0) { + throw new Error(`You passed key values but you 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/src/schemas/basicFilter.json b/src/schemas/basicFilter.json index dedb6f9..1b96ef0 100644 --- a/src/schemas/basicFilter.json +++ b/src/schemas/basicFilter.json @@ -31,7 +31,16 @@ "values": { "type": "array", "items": { - "type": ["string", "boolean", "number", "array"] + "type": ["string", "boolean", "number"] + } + }, + "keyValues": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": ["string", "boolean", "number"] + } } } }, diff --git a/src/schemas/basicFilterWithKeys.json b/src/schemas/basicFilterWithKeys.json new file mode 100644 index 0000000..f2d2bcf --- /dev/null +++ b/src/schemas/basicFilterWithKeys.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "target": { + "type": "object", + "properties": { + "table": { + "type": "string" + }, + "column": { + "type": "string" + }, + "keys": { + "type": "array", + "items": { + "type": ["string"] + } + } + }, + "required": [ + "table", + "column", + "keys" + ] + }, + "operator": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": ["string", "boolean", "number"] + } + }, + "keyValues": { + "type": "array", + "items": { + "type": ["array"] + } + } + }, + "required": [ + "target", + "operator", + "values" + ] +} \ No newline at end of file diff --git a/test/models.spec.ts b/test/models.spec.ts index 9823cee..924040b 100644 --- a/test/models.spec.ts +++ b/test/models.spec.ts @@ -514,10 +514,11 @@ describe("Unit | Filters", function () { it("should accept values as an array of tuples", function () { // Arrange - const values = [[1, 2], [3,4]]; + const values = [1, 2]; + const keyValues = [[1, 2], [3,4]]; // Act - const basicFilter = new models.BasicFilter({ table: "t", column: "c" , keys: ["1", "2"]}, "In", values); + const basicFilter = new models.BasicFilterWithKeys({ table: "t", column: "c" , keys: ["1", "2"]}, "In", values, keyValues); // Assert expect(basicFilter.values).toEqual(values); @@ -525,11 +526,12 @@ describe("Unit | Filters", function () { 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], [3,4]]; + const values = [1, 2]; + const keyValues = [[1, 2], [3,4]]; // Act const attemptToCreateFilter = () => { - return new models.BasicFilter({ table: "t", column: "c" , keys: ["1"]}, "In", values); + return new models.BasicFilterWithKeys({ table: "t", column: "c" , keys: ["1"]}, "In", values, keyValues); }; expect(attemptToCreateFilter).toThrowError(); }); @@ -706,7 +708,7 @@ describe("Unit | Filters", function () { // Arrange const testData = { basicFilter: new models.BasicFilter({ table: "a", column: "b" }, "In", ["x", "y"]), - basicFilterWithKeys: new models.BasicFilter({ table: "a", column: "b", keys: ["1", "2"] }, "In", [["x1", 1], ["y2",2]]), + 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" }