Merged PR 289793: Add QuickCreate models

Add QuickCreate models to support generate model thru quickCreate host

Related work items: #540005, #776450
This commit is contained in:
Sheng Liu 2022-08-18 19:13:56 +00:00
Родитель 4d57e9f0da
Коммит 64cbc6e3b7
8 изменённых файлов: 568 добавлений и 11 удалений

Просмотреть файл

@ -1 +1 @@
corembed
corembed

16
package-lock.json сгенерированный
Просмотреть файл

@ -1,12 +1,12 @@
{
"name": "powerbi-models",
"version": "1.11.0",
"version": "1.12.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "powerbi-models",
"version": "1.11.0",
"version": "1.12.0",
"license": "MIT",
"devDependencies": {
"@types/jasmine": "^3.5.5",
@ -8367,9 +8367,9 @@
}
},
"node_modules/terser": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz",
"integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==",
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
@ -16123,9 +16123,9 @@
"dev": true
},
"terser": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz",
"integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==",
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.2",

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "powerbi-models",
"version": "1.11.0",
"version": "1.12.0",
"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",

Просмотреть файл

@ -1120,6 +1120,80 @@ export interface IReportCreateConfiguration {
eventHooks?: EventHooks;
}
export interface IQuickCreateConfiguration {
type?: "quickCreate";
accessToken: string;
groupId?: string;
settings?: ISettings;
tokenType?: TokenType;
theme?: IReportTheme;
embedUrl?: string;
datasetCreateConfig: IDatasetCreateConfiguration;
reportCreationMode?: ReportCreationMode;
eventHooks?: EventHooks;
}
export interface IDatasetCreateConfiguration {
mashupDocument?: string;
locale: string;
datasourceConnectionConfig?: IDatasourceConnectionConfiguration;
tableSchemaList?: ITableSchema[];
data?: IDataTable[];
}
export enum ICredentialType {
NoConnection,
OnBehalfOf,
Anonymous,
}
export interface IDatasourceConnectionConfiguration {
path: string; // domain name, example "somedomain.dynamics.com"
kind: string; // dataSource kind, example: "CommonDataService"
dataCacheMode?: DataCacheMode; // DQ or Import
credentials?: ICredential;
}
export interface ICredential {
credentialType: ICredentialType;
credentialDetails?: { [property: string]: string };
}
export enum DataCacheMode {
Import,
DirectQuery,
}
export interface ITableSchema {
name: string;
columns: IColumnSchema[];
}
export interface IColumnSchema {
name: string;
dataType: DataType;
}
export const enum DataType {
Number = "Number",
Currency = "Currency",
Int32 = "Int32",
Percentage = "Percentage",
DateTime = "DateTime",
DateTimeZone = "DateTimeZone",
Date = "Date",
Time = "Time",
Duration = "Duration",
Text = "Text",
Logical = "Logical",
Binary = "Binary",
}
export interface IDataTable {
name: string;
rows: string[][];
}
/**
* @deprecated
*/
@ -1150,6 +1224,11 @@ export interface ILocaleSettings {
formatLocale?: string;
}
export const enum ReportCreationMode {
Default = "Default",
QuickExplore = "QuickExplore",
}
export interface ISettings {
authoringHintsEnabled?: boolean;
background?: BackgroundType;
@ -1805,6 +1884,11 @@ export function validateCreateReport(input: any): IError[] {
return errors ? errors.map(normalizeError) : undefined;
}
export function validateQuickCreate(input: any): IError[] {
const errors: any[] = Validators.quickCreateValidator.validate(input);
return errors ? errors.map(normalizeError) : undefined;
}
export function validateDashboardLoad(input: any): IError[] {
const errors: any[] = Validators.dashboardLoadValidator.validate(input);
return errors ? errors.map(normalizeError) : undefined;

Просмотреть файл

@ -62,6 +62,8 @@ import { FieldRequiredValidator } from './fieldRequiredValidator';
import { MapValidator } from './mapValidator';
import { ArrayValidator, BooleanArrayValidator, BooleanValidator, EnumValidator, NumberArrayValidator, NumberValidator, RangeValidator, StringArrayValidator, StringValidator } from './typeValidator';
import { ParametersPanelValidator } from '../models/parameterPanelValidator';
import { DatasetCreateConfigValidator, ColumnSchemaValidator, DatasourceConnectionConfigValidator, TableSchemaValidator, TableDataValidator } from '../models/datasetCreateConfigValidator';
import { QuickCreateValidator } from '../models/quickCreateValidator';
export interface IValidationError {
path?: string;
@ -73,6 +75,11 @@ export interface IValidator {
validate(input: any, path?: string, fieldName?: string): IValidationError[];
}
export interface IFieldValidatorsPair {
field: string;
validators: IValidator[];
}
export const Validators = {
addBookmarkRequestValidator: new AddBookmarkRequestValidator(),
advancedFilterTypeValidator: new EnumValidator([0]),
@ -93,6 +100,7 @@ export const Validators = {
bookmarksPaneValidator: new BookmarksPaneValidator(),
captureBookmarkOptionsValidator: new CaptureBookmarkOptionsValidator(),
captureBookmarkRequestValidator: new CaptureBookmarkRequestValidator(),
columnSchemaArrayValidator: new ArrayValidator([new ColumnSchemaValidator()]),
commandDisplayOptionValidator: new EnumValidator([0, 1, 2]),
commandExtensionSelectorValidator: new AnyOfValidator([new VisualSelectorValidator(), new VisualTypeSelectorValidator()]),
commandExtensionArrayValidator: new ArrayValidator([new CommandExtensionValidator()]),
@ -107,6 +115,8 @@ export const Validators = {
customThemeValidator: new CustomThemeValidator(),
dashboardLoadValidator: new DashboardLoadValidator(),
datasetBindingValidator: new DatasetBindingValidator(),
datasetCreateConfigValidator: new DatasetCreateConfigValidator(),
datasourceConnectionConfigValidator: new DatasourceConnectionConfigValidator(),
displayStateModeValidator: new EnumValidator([0, 1]),
displayStateValidator: new DisplayStateValidator(),
exportDataRequestValidator: new ExportDataRequestValidator(),
@ -163,6 +173,8 @@ export const Validators = {
qnaInterpretInputDataValidator: new QnaInterpretInputDataValidator(),
qnaPanesValidator: new QnaPanesValidator(),
qnaSettingValidator: new QnaSettingsValidator(),
quickCreateValidator: new QuickCreateValidator(),
rawDataValidator: new ArrayValidator([new ArrayValidator([new StringValidator()])]),
relativeDateFilterOperatorValidator: new EnumValidator([0, 1, 2]),
relativeDateFilterTimeUnitTypeValidator: new EnumValidator([0, 1, 2, 3, 4, 5, 6]),
relativeDateFilterTypeValidator: new EnumValidator([4]),
@ -188,6 +200,8 @@ export const Validators = {
stringArrayValidator: new StringArrayValidator(),
stringValidator: new StringValidator(),
syncSlicersPaneValidator: new SyncSlicersPaneValidator(),
tableDataArrayValidator: new ArrayValidator([new TableDataValidator()]),
tableSchemaListValidator: new ArrayValidator([new TableSchemaValidator()]),
tileLoadValidator: new TileLoadValidator(),
tokenTypeValidator: new EnumValidator([0, 1]),
topNFilterTypeValidator: new EnumValidator([5]),

Просмотреть файл

@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { IFieldValidatorsPair, MultipleFieldsValidator } from '../core/multipleFieldsValidator';
import { ObjectValidator } from '../core/typeValidator';
import { IValidationError, Validators } from '../core/validator';
export class DatasetCreateConfigValidator extends ObjectValidator {
public validate(input: any, path?: string, field?: string): IValidationError[] {
if (input == null) {
return null;
}
let errors = super.validate(input, path, field);
if (errors) {
return errors;
}
const fields: IFieldValidatorsPair[] = [
{
field: "locale",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
},
{
field: "mashupDocument",
validators: [Validators.stringValidator]
},
{
field: "datasourceConnectionConfig",
validators: [Validators.datasourceConnectionConfigValidator]
},
{
field: "tableSchemaList",
validators: [Validators.tableSchemaListValidator]
},
{
field: "data",
validators: [Validators.tableDataArrayValidator]
},
];
const multipleFieldsValidator = new MultipleFieldsValidator(fields);
errors = multipleFieldsValidator.validate(input, path, field);
if (errors){
return errors;
}
if (input["datasourceConnectionConfig"] && input["mashupDocument"] == null) {
return [{
message: "mashupDocument cannot be empty when datasourceConnectionConfig is presented"
}];
}
if (input["data"] && input["tableSchemaList"] == null) {
return [{
message: "tableSchemaList cannot be empty when data is provided"
}];
}
if (input["data"] == null && input["mashupDocument"] == null) {
return [{
message: "At least one of data or mashupDocument must be provided"
}];
}
}
}
export class DatasourceConnectionConfigValidator 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: "path",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
},
{
field: "kind",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
}
];
const multipleFieldsValidator = new MultipleFieldsValidator(fields);
return multipleFieldsValidator.validate(input, path, field);
}
}
export class ColumnSchemaValidator 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: "name",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
},
{
field: "dataType",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
}
];
const multipleFieldsValidator = new MultipleFieldsValidator(fields);
return multipleFieldsValidator.validate(input, path, field);
}
}
export class TableSchemaValidator 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: "name",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
},
{
field: "columns",
validators: [Validators.fieldRequiredValidator, Validators.columnSchemaArrayValidator]
}
];
const multipleFieldsValidator = new MultipleFieldsValidator(fields);
return multipleFieldsValidator.validate(input, path, field);
}
}
export class TableDataValidator 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: "name",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
},
{
field: "rows",
validators: [Validators.fieldRequiredValidator, Validators.rawDataValidator]
}
];
const multipleFieldsValidator = new MultipleFieldsValidator(fields);
return multipleFieldsValidator.validate(input, path, field);
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { IFieldValidatorsPair, MultipleFieldsValidator } from '../core/multipleFieldsValidator';
import { ObjectValidator } from '../core/typeValidator';
import { IValidationError, Validators } from '../core/validator';
export class QuickCreateValidator extends ObjectValidator {
public validate(input: any, path?: string, field?: string): IValidationError[] {
if (input == null) {
return null;
}
let errors = super.validate(input, path, field);
if (errors) {
return errors;
}
const fields: IFieldValidatorsPair[] = [
{
field: "accessToken",
validators: [Validators.fieldRequiredValidator, Validators.stringValidator]
},
{
field: "groupId",
validators: [Validators.stringValidator]
},
{
field: "tokenType",
validators: [Validators.tokenTypeValidator]
},
{
field: "theme",
validators: [Validators.customThemeValidator]
},
{
field: "datasetCreateConfig",
validators: [Validators.fieldRequiredValidator, Validators.datasetCreateConfigValidator]
},
];
const multipleFieldsValidator = new MultipleFieldsValidator(fields);
return multipleFieldsValidator.validate(input, path, field);
}
}

Просмотреть файл

@ -3,7 +3,7 @@
import { Validators } from '../src/validators/core/validator';
import * as models from '../src/models';
import { IFilter, ITarget } from '../src/models';
import { IFilter, ITarget, IQuickCreateConfiguration } from '../src/models';
describe('Unit | Models', () => {
function testForExpectedMessage(errors: models.IError[], message: string): void {
@ -385,6 +385,246 @@ describe('Unit | Models', () => {
});
});
describe('validateQuickCreate', () => {
const accessTokenRequiredMessage = "accessToken is required";
const accessTokenInvalidTypeMessage = "accessToken must be a string";
const rawDataInvalidMessage = "data property is invalid";
const rawDataNoSchemaErrorMessage = "tableSchemaList cannot be empty when data is provided";
const rawDataAndMashupMissingErrorMessage = "At least one of data or mashupDocument must be provided";
const datasourceConfigNoMeshupErrorMessage = "mashupDocument cannot be empty when datasourceConnectionConfig is presented";
it(`should return errors with one containing message '${accessTokenRequiredMessage}' if accessToken is not defined`, () => {
// Arrange
const testData = {
load: {
}
};
// Act
const errors = models.validateQuickCreate(testData.load);
// Assert
testForExpectedMessage(errors, accessTokenRequiredMessage);
});
it(`should return errors with one containing message '${accessTokenInvalidTypeMessage}' if accessToken is not a string`, () => {
// Arrange
const testData = {
load: {
accessToken: 1
}
};
// Act
const errors = models.validateQuickCreate(testData.load);
// Assert
testForExpectedMessage(errors, accessTokenInvalidTypeMessage);
});
describe('datasetCreateConfig', () => {
it(`happy path`, () => {
// Arrange
const testData = {
load: {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US",
mashupDocument: "document",
datasourceConnectionConfig: {
path: "somedomain.dynamics.com",
kind: "CommonDataService",
},
}
}
};
// Act
const errors = models.validateQuickCreate(testData.load);
// Assert
expect(errors).toBeUndefined();
});
it(`should fail when datasourceConnectionConfig is incomplete`, () => {
// Arrange
const testData = {
load: {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US",
datasourceConnectionConfig: {
path: "somedomain.dynamics.com",
},
mashupDocument: "document",
}
}
};
// Act
const errors = models.validateQuickCreate(testData.load);
// Assert
testForExpectedMessage(errors, "kind is required");
});
it(`dataset with raw data`, () => {
// Arrange
const testData = {
load: {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US",
tableSchemaList: [{
name: "Table",
columns: [{
name: "fieldname",
dataType: models.DataType.Int32,
}]
}],
data: [{
name: "Table",
rows: [['test','1']]
}],
}
}
};
// Act
const errors = models.validateQuickCreate(testData.load);
// Assert
expect(errors).toBeUndefined();
});
it(`dataset with raw data without schema`, () => {
// Arrange
const testData = {
load: {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US",
data: [{
name: "Table",
rows: [['test','1']]
}],
}
}
};
// Act
const errors = models.validateQuickCreate(testData.load);
// Assert
testForExpectedMessage(errors, rawDataNoSchemaErrorMessage);
});
it(`should fail when raw data has invalid fields`, () => {
// Arrange
const testData = {
load: {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US",
data: [{
name: "Table",
rows: [['test', 20]]
}],
}
}
};
// Act
const errors = models.validateQuickCreate(testData.load);
// Assert
testForExpectedMessage(errors, rawDataInvalidMessage);
});
it(`should fail when raw data has invalid schema`, () => {
// Arrange
const testData = {
load: {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US",
data: [{
name: "Table",
rows: ['test']
}],
}
}
};
// Act
const errors = models.validateQuickCreate(testData.load);
// Assert
testForExpectedMessage(errors, rawDataInvalidMessage);
});
it(`dataset with table schema`, () => {
// Arrange
const testData: IQuickCreateConfiguration = {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US",
mashupDocument: "testdoc",
tableSchemaList: [{
name: "Table",
columns: [{
name: "fieldname",
dataType: models.DataType.Int32,
}]
}]
}
};
// Act
const errors = models.validateQuickCreate(testData);
// Assert
expect(errors).toBeUndefined();
});
it(`dataset with no mashup no raw data`, () => {
// Arrange
const testData: IQuickCreateConfiguration = {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US"
}
};
// Act
const errors = models.validateQuickCreate(testData);
// Assert
testForExpectedMessage(errors, rawDataAndMashupMissingErrorMessage);
});
it(`dataset with datasourceConfig no mashup`, () => {
// Arrange
const testData: IQuickCreateConfiguration = {
accessToken: "fakeToken",
datasetCreateConfig: {
locale: "en_US",
datasourceConnectionConfig: {
path: "somedomain.dynamics.com",
kind: "CommonDataService",
}
}
};
// Act
const errors = models.validateQuickCreate(testData);
// Assert
testForExpectedMessage(errors, datasourceConfigNoMeshupErrorMessage);
});
});
});
describe('validatePaginatedReportLoad', () => {
const testData: any = {
accessToken: "token",