Коммит
90dc0df685
27
README.md
27
README.md
|
@ -102,4 +102,31 @@ const advancedFilter = new models.AdvancedFilter(
|
|||
value: "Microsoft"
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Date Formatting
|
||||
Dates should be formated using [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) standard. Example: `2016-09-08T00:15:46.861Z`
|
||||
|
||||
This is how dates are naturally serialized to JSON:
|
||||
```
|
||||
new Date().toJSON(); //=> 2016-09-08T00:15:46.861Z
|
||||
```
|
||||
|
||||
An example filter using this Date format would look like the following:
|
||||
|
||||
```
|
||||
{
|
||||
"$schema": "http://powerbi.com/product/schema#advanced",
|
||||
"target": {
|
||||
"table": "Time",
|
||||
"column": "Date"
|
||||
},
|
||||
"logicalOperator": "And",
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "GreaterThan",
|
||||
"value": "2014-06-01T07:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
|
@ -155,12 +155,7 @@ gulp.task('tslint:build', 'Run TSLint on src', function () {
|
|||
gulp.task('tslint:test', 'Run TSLint on src and tests', function () {
|
||||
return gulp.src(["src/**/*.ts", "test/**/*.ts"])
|
||||
.pipe(tslint({
|
||||
formatter: "verbose",
|
||||
configuration: {
|
||||
rules: {
|
||||
"no-console": false
|
||||
}
|
||||
}
|
||||
formatter: "verbose"
|
||||
}))
|
||||
.pipe(tslint.report());
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "powerbi-models",
|
||||
"version": "0.7.4",
|
||||
"version": "0.9.1",
|
||||
"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",
|
||||
|
|
|
@ -3,7 +3,8 @@ declare var require: Function;
|
|||
/* tslint:disable:no-var-requires */
|
||||
export const advancedFilterSchema = require('./schemas/advancedFilter.json');
|
||||
export const filterSchema = require('./schemas/filter.json');
|
||||
export const loadSchema = require('./schemas/load.json');
|
||||
export const loadSchema = require('./schemas/reportLoadConfiguration.json');
|
||||
export const dashboardLoadSchema = require('./schemas/dashboardLoadConfiguration.json');
|
||||
export const pageSchema = require('./schemas/page.json');
|
||||
export const settingsSchema = require('./schemas/settings.json');
|
||||
export const basicFilterSchema = require('./schemas/basicFilter.json');
|
||||
|
@ -22,14 +23,15 @@ export interface IError {
|
|||
}
|
||||
|
||||
function normalizeError(error: IValidationError): IError {
|
||||
if (!error.message) {
|
||||
error.message = `${error.path} is invalid. Not meeting ${error.keyword} constraint`;
|
||||
let message = error.message;
|
||||
|
||||
if (!message) {
|
||||
message = `${error.path} is invalid. Not meeting ${error.keyword} constraint`;
|
||||
}
|
||||
|
||||
delete error.path;
|
||||
delete error.keyword;
|
||||
|
||||
return error;
|
||||
return {
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +64,7 @@ export const validateSettings = validate(settingsSchema, {
|
|||
}
|
||||
});
|
||||
|
||||
export interface ILoadConfiguration {
|
||||
export interface IReportLoadConfiguration {
|
||||
accessToken: string;
|
||||
id: string;
|
||||
settings?: ISettings;
|
||||
|
@ -70,7 +72,7 @@ export interface ILoadConfiguration {
|
|||
filters?: (IBasicFilter | IAdvancedFilter)[];
|
||||
}
|
||||
|
||||
export const validateLoad = validate(loadSchema, {
|
||||
export const validateReportLoad = validate(loadSchema, {
|
||||
schemas: {
|
||||
settings: settingsSchema,
|
||||
basicFilter: basicFilterSchema,
|
||||
|
@ -78,6 +80,13 @@ export const validateLoad = validate(loadSchema, {
|
|||
}
|
||||
});
|
||||
|
||||
export interface IDashboardLoadConfiguration {
|
||||
accessToken: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const validateDashboardLoad = validate(dashboardLoadSchema);
|
||||
|
||||
export interface IReport {
|
||||
id: string;
|
||||
displayName: string;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accessToken": {
|
||||
"type": "string",
|
||||
"messages": {
|
||||
"type": "accessToken must be a string",
|
||||
"required": "accessToken is required"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"messages": {
|
||||
"type": "id must be a string",
|
||||
"required": "id is required"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessToken",
|
||||
"id"
|
||||
]
|
||||
}
|
|
@ -7,16 +7,14 @@
|
|||
"messages": {
|
||||
"type": "accessToken must be a string",
|
||||
"required": "accessToken is required"
|
||||
},
|
||||
"invalidMessage": "accessToken property is invalid"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"messages": {
|
||||
"type": "id must be a string",
|
||||
"required": "id is required"
|
||||
},
|
||||
"invalidMessage": "id property is invalid"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#settings"
|
||||
|
@ -29,14 +27,17 @@
|
|||
},
|
||||
"filters": {
|
||||
"type": "array",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#basicFilter"
|
||||
},
|
||||
{
|
||||
"$ref": "#advancedFilter"
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#basicFilter"
|
||||
},
|
||||
{
|
||||
"$ref": "#advancedFilter"
|
||||
}
|
||||
]
|
||||
},
|
||||
"invalidMessage": "filters property is invalid"
|
||||
}
|
||||
},
|
|
@ -3,15 +3,13 @@ import * as models from '../src/models';
|
|||
describe('Unit | Models', function () {
|
||||
function testForExpectedMessage(errors: models.IError[], message: string) {
|
||||
expect(errors).toBeDefined();
|
||||
errors
|
||||
.forEach(error => {
|
||||
if (error.message === message) {
|
||||
expect(true).toBe(true);
|
||||
}
|
||||
});
|
||||
const atLeastOneMessageMatches = errors
|
||||
.some(error => error.message === message);
|
||||
|
||||
expect(atLeastOneMessageMatches).toBe(true);
|
||||
}
|
||||
|
||||
describe('validateLoad', function () {
|
||||
describe('validateReportLoad', function () {
|
||||
const accessTokenRequiredMessage = models.loadSchema.properties.accessToken.messages.required;
|
||||
const accessTokenInvalidTypeMessage = models.loadSchema.properties.accessToken.messages.type;
|
||||
const idRequiredMessage = models.loadSchema.properties.id.messages.required;
|
||||
|
@ -27,7 +25,7 @@ describe('Unit | Models', function () {
|
|||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateLoad(testData.load);
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, accessTokenRequiredMessage);
|
||||
|
@ -42,7 +40,7 @@ describe('Unit | Models', function () {
|
|||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateLoad(testData.load);
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, accessTokenInvalidTypeMessage);
|
||||
|
@ -52,11 +50,12 @@ describe('Unit | Models', function () {
|
|||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
accessToken: "fakeToken"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateLoad(testData.load);
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, idRequiredMessage);
|
||||
|
@ -66,14 +65,16 @@ describe('Unit | Models', function () {
|
|||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
accessToken: "fakeToken",
|
||||
id: 1
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateLoad(testData.load);
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, idRequiredMessage);
|
||||
testForExpectedMessage(errors, idInvalidTypeMessage);
|
||||
});
|
||||
|
||||
it(`should return undefined if id and accessToken are provided`, function () {
|
||||
|
@ -86,7 +87,7 @@ describe('Unit | Models', function () {
|
|||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateLoad(testData.load);
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
expect(errors).toBeUndefined();
|
||||
|
@ -103,12 +104,73 @@ describe('Unit | Models', function () {
|
|||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateLoad(testData.load);
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, filtersInvalidMessage);
|
||||
});
|
||||
|
||||
it(`should return errors if filters is array, but item is not a valid basicFilter or advancedFilter`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
id: 'fakeId',
|
||||
accessToken: 'fakeAccessToken',
|
||||
filters: [
|
||||
{ x: 1 }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
expect(errors.length > 0).toBe(true);
|
||||
});
|
||||
|
||||
// TODO: Need to fix reportLoadConfiguration.json schema so that this fails.
|
||||
// Currently this validates without errors, but the second object should be rejected since it is not a valid filter.
|
||||
xit(`should return errors if filters is array, but not all items are valid basicFilter or advancedFilter`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
id: 'fakeId',
|
||||
accessToken: 'fakeAccessToken',
|
||||
filters: [
|
||||
new models.BasicFilter({ table: "fakeTable", column: "fakeColumn" }, "In", ["A"]).toJSON(),
|
||||
{ x: 1 }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it(`should return undefined if filters is valid array of basicFilter or advancedFilter`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
id: 'fakeId',
|
||||
accessToken: 'fakeAccessToken',
|
||||
filters: [
|
||||
new models.BasicFilter({ table: "fakeTable", column: "fakeColumn" }, "In", ["A"]).toJSON()
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
expect(errors).toBeUndefined();
|
||||
});
|
||||
|
||||
it(`should return errors with one containing message '${pageNameInvalidTypeMessage}' if pageName is not a string`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
|
@ -120,13 +182,96 @@ describe('Unit | Models', function () {
|
|||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateLoad(testData.load);
|
||||
const errors = models.validateReportLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, pageNameInvalidTypeMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateDashboardLoad', function () {
|
||||
const accessTokenRequiredMessage = models.dashboardLoadSchema.properties.accessToken.messages.required;
|
||||
const accessTokenInvalidTypeMessage = models.dashboardLoadSchema.properties.accessToken.messages.type;
|
||||
const idRequiredMessage = models.dashboardLoadSchema.properties.id.messages.required;
|
||||
const idInvalidTypeMessage = models.dashboardLoadSchema.properties.id.messages.type;
|
||||
|
||||
it(`should return errors with one containing message '${accessTokenRequiredMessage}' if accessToken is not defined`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateDashboardLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, accessTokenRequiredMessage);
|
||||
});
|
||||
|
||||
it(`should return errors with one containing message '${accessTokenInvalidTypeMessage}' if accessToken is not a string`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
accessToken: 1
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateDashboardLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, accessTokenInvalidTypeMessage);
|
||||
});
|
||||
|
||||
it(`should return errors with one containing message '${idRequiredMessage}' if id is not defined`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
accessToken: "fakeToken"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateDashboardLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, idRequiredMessage);
|
||||
});
|
||||
|
||||
it(`should return errors with one containing message '${idInvalidTypeMessage}' if id is not a string`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
accessToken: 'fakeAccessToken',
|
||||
id: 1
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateDashboardLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
testForExpectedMessage(errors, idInvalidTypeMessage);
|
||||
});
|
||||
|
||||
it(`should return undefined if id and accessToken are provided`, function () {
|
||||
// Arrange
|
||||
const testData = {
|
||||
load: {
|
||||
id: 'fakeId',
|
||||
accessToken: 'fakeAccessToken'
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const errors = models.validateDashboardLoad(testData.load);
|
||||
|
||||
// Assert
|
||||
expect(errors).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateFilter', function () {
|
||||
it("should return errors if object does not validate against schema", function () {
|
||||
// Arrange
|
||||
|
|
Загрузка…
Ссылка в новой задаче