From b55b3e17bc3d690a91c10728fd7d84a8e74640bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?william=20chang=28=E5=BC=B5=E4=BB=B2=E5=A8=81=29?= Date: Tue, 9 Apr 2024 18:27:24 +0800 Subject: [PATCH] feat(wren-ui/api): add view to manifest --- wren-ui/src/apollo/server/mdl/mdlBuilder.ts | 26 ++- .../apollo/server/mdl/test/mdlBuilder.test.ts | 199 +++++++++++++++++- wren-ui/src/apollo/server/mdl/type.ts | 6 +- .../src/apollo/server/services/mdlService.ts | 7 + 4 files changed, 232 insertions(+), 6 deletions(-) diff --git a/wren-ui/src/apollo/server/mdl/mdlBuilder.ts b/wren-ui/src/apollo/server/mdl/mdlBuilder.ts index 3044cabe0..64f45dad1 100644 --- a/wren-ui/src/apollo/server/mdl/mdlBuilder.ts +++ b/wren-ui/src/apollo/server/mdl/mdlBuilder.ts @@ -1,5 +1,11 @@ import { isEmpty } from 'lodash'; -import { Model, ModelColumn, Project, RelationInfo } from '../repositories'; +import { + Model, + ModelColumn, + Project, + RelationInfo, + View, +} from '../repositories'; import { Manifest, ModelMDL } from './type'; import { getLogger } from '@server/utils'; @@ -11,6 +17,7 @@ export interface MDLBuilderBuildFromOptions { models: Model[]; columns?: ModelColumn[]; relations?: RelationInfo[]; + views: View[]; relatedModels?: Model[]; relatedColumns?: ModelColumn[]; relatedRelations?: RelationInfo[]; @@ -28,6 +35,7 @@ export class MDLBuilder implements IMDLBuilder { private readonly models: Model[]; private readonly columns: ModelColumn[]; private readonly relations: RelationInfo[]; + private readonly views: View[]; // related models, columns, and relations are used as the reference to build calculatedField expression or other // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -43,6 +51,7 @@ export class MDLBuilder implements IMDLBuilder { models, columns, relations, + views, relatedModels, relatedColumns, relatedRelations, @@ -51,6 +60,7 @@ export class MDLBuilder implements IMDLBuilder { this.models = models; this.columns = columns; this.relations = relations; + this.views = views; this.relatedModels = relatedModels; this.relatedColumns = relatedColumns; this.relatedRelations = relatedRelations; @@ -64,6 +74,7 @@ export class MDLBuilder implements IMDLBuilder { this.addModel(); this.addColumn(); this.addRelation(); + this.addView(); return this.getManifest(); } @@ -88,6 +99,19 @@ export class MDLBuilder implements IMDLBuilder { }); } + public addView(): void { + if (!isEmpty(this.manifest.views)) { + return; + } + this.manifest.views = this.views.map((view: View) => { + return { + name: view.name, + statement: view.statement, + properties: JSON.parse(view.properties), + }; + }); + } + public addColumn(): void { // should addModel first if (isEmpty(this.manifest.models)) { diff --git a/wren-ui/src/apollo/server/mdl/test/mdlBuilder.test.ts b/wren-ui/src/apollo/server/mdl/test/mdlBuilder.test.ts index 28a1f7e4b..e43aca054 100644 --- a/wren-ui/src/apollo/server/mdl/test/mdlBuilder.test.ts +++ b/wren-ui/src/apollo/server/mdl/test/mdlBuilder.test.ts @@ -1,6 +1,6 @@ -import { Model, Project, ModelColumn, RelationInfo } from '../../repositories'; +import { Model, Project, ModelColumn, RelationInfo, View } from '../../repositories'; import { MDLBuilder, MDLBuilderBuildFromOptions } from '../mdlBuilder'; -import { ModelMDL, RelationMDL } from '../type'; +import { ModelMDL, RelationMDL, ViewMDL } from '../type'; describe('MDLBuilder', () => { let mdlBuilder: MDLBuilder; @@ -144,6 +144,7 @@ describe('MDLBuilder', () => { relationship: 'OrderCustomer', properties: null, notNull: false, + properties: null, }, ], cached: false, @@ -192,4 +193,198 @@ describe('MDLBuilder', () => { expect(manifest.relationships).toEqual(expectedRelationships); }); }); + + it('should return a manifest with models & columns & relations & views.', () => { + // Arrange + const project = { + id: 1, + type: 'bigquery', + displayName: 'my project', + projectId: 'bq-project-id', + datasetId: 'my-dataset', + credentials: 'my-credential', + catalog: 'tbd', + schema: 'tbd', + sampleDataset: null, + } as Project; + const models = [ + { + id: 1, + projectId: 1, + displayName: 'order', + sourceTableName: 'order', + referenceName: 'order', + refSql: 'SELECT * FROM order', + cached: false, + refreshTime: null, + properties: JSON.stringify({ description: 'foo table' }), + }, + { + id: 2, + projectId: 1, + displayName: 'customer', + sourceTableName: 'customer', + referenceName: 'customer', + refSql: 'SELECT * FROM customer', + cached: false, + refreshTime: null, + properties: null, + }, + ] as Model[]; + const columns = [ + { + id: 1, + modelId: 1, + isCalculated: false, + displayName: 'orderKey', + referenceName: 'orderKey', + sourceColumnName: 'orderKey', + aggregation: null, + lineage: null, + diagram: null, + customExpression: null, + type: 'STRING', + notNull: true, + isPk: true, + properties: JSON.stringify({ description: 'bar' }), + }, + { + id: 2, + modelId: 2, + isCalculated: false, + displayName: 'orderKey', + referenceName: 'orderKey', + sourceColumnName: 'orderKey', + aggregation: null, + lineage: null, + diagram: null, + customExpression: null, + type: 'STRING', + notNull: true, + isPk: false, + properties: null, + }, + ] as ModelColumn[]; + const relations = [ + { + id: 1, + projectId: 1, + name: 'OrderCustomer', + fromColumnId: 1, + toColumnId: 2, + joinType: 'oneToMany', + fromModelId: 1, + fromModelName: 'order', + fromColumnName: 'orderKey', + toModelId: 2, + toModelName: 'customer', + toColumnName: 'orderKey', + }, + ] as RelationInfo[]; + const views = [ + { + id: 1, + projectId: 1, + name: 'view', + statement: 'select * from order', + cached: false, + properties: JSON.stringify({ + description: 'foo view', + displayName: 'view', + }), + }, + ] as View[]; + + const builderOptions = { + project, + models, + views, + columns, + relations, + relatedModels: [], + relatedColumns: [], + relatedRelations: [], + } as MDLBuilderBuildFromOptions; + mdlBuilder = new MDLBuilder(builderOptions); + + // Act + const manifest = mdlBuilder.build(); + + // Assert + const expectedModels = [ + { + name: 'order', + refSql: 'SELECT * FROM order', + columns: [ + { + name: 'orderKey', + expression: '', + type: 'STRING', + isCalculated: false, + notNull: true, + properties: { description: 'bar' }, + }, + { + name: 'customer', + type: 'customer', + isCalculated: false, + relationship: 'OrderCustomer', + notNull: false, + properties: null, + }, + ], + cached: false, + refreshTime: null, + primaryKey: 'orderKey', + properties: { description: 'foo table' }, + }, + { + name: 'customer', + refSql: 'SELECT * FROM customer', + columns: [ + { + name: 'orderKey', + expression: '', + type: 'STRING', + isCalculated: false, + notNull: true, + properties: null, + }, + { + name: 'order', + type: 'order', + isCalculated: false, + relationship: 'OrderCustomer', + notNull: false, + properties: null, + }, + ], + primaryKey: '', + cached: false, + refreshTime: null, + properties: null, + }, + ] as ModelMDL[]; + + const expectedRelationships = [ + { + name: 'OrderCustomer', + models: ['order', 'customer'], + joinType: 'oneToMany', + condition: 'order.orderKey = customer.orderKey', + }, + ] as RelationMDL[]; + + const expectedViews = [ + { + name: 'view', + statement: 'select * from order', + properties: { description: 'foo view', displayName: 'view' }, + }, + ] as ViewMDL[]; + + expect(manifest.models).toEqual(expectedModels); + expect(manifest.relationships).toEqual(expectedRelationships); + expect(manifest.views).toEqual(expectedViews); + }); }); diff --git a/wren-ui/src/apollo/server/mdl/type.ts b/wren-ui/src/apollo/server/mdl/type.ts index 1a4c0e500..e1ee29d6f 100644 --- a/wren-ui/src/apollo/server/mdl/type.ts +++ b/wren-ui/src/apollo/server/mdl/type.ts @@ -52,11 +52,11 @@ export interface EnumDefinition { }; } -export interface View { +export interface ViewMDL { name: string; statement: string; - description?: string; properties?: { + displayName: string; description?: string; }; } @@ -67,5 +67,5 @@ export interface Manifest { models?: ModelMDL[]; relationships?: RelationMDL[]; enumDefinitions?: EnumDefinition[]; - views?: View[]; + views?: ViewMDL[]; } diff --git a/wren-ui/src/apollo/server/services/mdlService.ts b/wren-ui/src/apollo/server/services/mdlService.ts index 82b50d420..cfb777663 100644 --- a/wren-ui/src/apollo/server/services/mdlService.ts +++ b/wren-ui/src/apollo/server/services/mdlService.ts @@ -4,6 +4,7 @@ import { IModelRepository, IProjectRepository, IRelationRepository, + IViewRepository, } from '../repositories'; import { Manifest } from '../mdl/type'; export interface IMDLService { @@ -13,22 +14,26 @@ export interface IMDLService { export class MDLService implements IMDLService { private projectRepository: IProjectRepository; private modelRepository: IModelRepository; + private viewRepository: IViewRepository; private modelColumnRepository: IModelColumnRepository; private relationRepository: IRelationRepository; constructor({ projectRepository, modelRepository, + viewRepository, modelColumnRepository, relationRepository, }: { projectRepository: IProjectRepository; modelRepository: IModelRepository; + viewRepository: IViewRepository; modelColumnRepository: IModelColumnRepository; relationRepository: IRelationRepository; }) { this.projectRepository = projectRepository; this.modelRepository = modelRepository; + this.viewRepository = viewRepository; this.modelColumnRepository = modelColumnRepository; this.relationRepository = relationRepository; } @@ -37,6 +42,7 @@ export class MDLService implements IMDLService { const project = await this.projectRepository.getCurrentProject(); const projectId = project.id; const models = await this.modelRepository.findAllBy({ projectId }); + const views = await this.viewRepository.findAllBy({ projectId }); const modelIds = models.map((m) => m.id); const columns = await this.modelColumnRepository.findColumnsByModelIds(modelIds); @@ -49,6 +55,7 @@ export class MDLService implements IMDLService { const mdlBuilder = new MDLBuilder({ project, models, + views, columns, relations, relatedModels,