-
Notifications
You must be signed in to change notification settings - Fork 182
perf: send a schema reference in get-block-data instead of the full schema #6402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ import { AbstractUIBlockComponent } from '../models/abstract-ui-block.component' | |||||
| import { PolicyHelper } from 'src/app/services/policy-helper.service'; | ||||||
| import { RequestDocumentBlockDialog } from './dialog/request-document-block-dialog.component'; | ||||||
| import { SchemaRulesService } from 'src/app/services/schema-rules.service'; | ||||||
| import { SchemaService } from 'src/app/services/schema.service'; | ||||||
| import { audit, finalize, takeUntil } from 'rxjs/operators'; | ||||||
| import { interval, Subject, Subscription, firstValueFrom } from 'rxjs'; | ||||||
| import { prepareVcData } from 'src/app/modules/common/models/prepare-vc-data'; | ||||||
|
|
@@ -151,6 +152,7 @@ export class RequestDocumentBlockComponent | |||||
| private tablePersist: TablePersistenceService, | ||||||
| private ipfsService: IPFSService, | ||||||
| private policyTest: PolicyTestAutomationService, | ||||||
| private schemaService: SchemaService, | ||||||
| ) { | ||||||
| super(policyEngineService, profile, wsService); | ||||||
| this.dataForm = this.fb.group({}); | ||||||
|
|
@@ -209,6 +211,24 @@ export class RequestDocumentBlockComponent | |||||
| } | ||||||
|
|
||||||
| protected override _onSuccess(data: any) { | ||||||
| // A schema reference (id, no document) is resolved to the full schema once | ||||||
| // before the render flow; a full schema is used as-is. | ||||||
| const schemaRef = data?.schema; | ||||||
| if (schemaRef && !schemaRef.document && schemaRef.id) { | ||||||
| this.loading = true; | ||||||
| this.schemaService.resolveSchemaById(schemaRef.id).subscribe({ | ||||||
| next: (full) => { | ||||||
| data.schema = full; | ||||||
| this._applyBlockData(data); | ||||||
| }, | ||||||
| error: () => this._applyBlockData(data) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On resolution failure this renders with the bare reference schema.
Suggested change
|
||||||
| }); | ||||||
| return; | ||||||
| } | ||||||
| this._applyBlockData(data); | ||||||
| } | ||||||
|
|
||||||
| private _applyBlockData(data: any) { | ||||||
| this.setData(data); | ||||||
| if (this.type === 'dialog') { | ||||||
| setTimeout(() => { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; | ||
| import { TestBed } from '@angular/core/testing'; | ||
| import { SchemaService } from './schema.service'; | ||
| import { AuthService } from './auth.service'; | ||
| import { API_BASE_URL } from './api'; | ||
|
|
||
| const SINGLE_URL = `${API_BASE_URL}/schema`; | ||
|
|
||
| describe('SchemaService single-schema resolution', () => { | ||
| let service: SchemaService; | ||
| let httpMock: HttpTestingController; | ||
|
|
||
| beforeEach(() => { | ||
| TestBed.configureTestingModule({ | ||
| imports: [HttpClientTestingModule], | ||
| providers: [ | ||
| SchemaService, | ||
| { provide: AuthService, useValue: {} }, | ||
| ], | ||
| }); | ||
| service = TestBed.inject(SchemaService); | ||
| httpMock = TestBed.inject(HttpTestingController); | ||
| }); | ||
|
|
||
| afterEach(() => httpMock.verify()); | ||
|
|
||
| it('getSchemaById GETs /schema/:id', () => { | ||
| service.getSchemaById('s1').subscribe(); | ||
| const req = httpMock.expectOne(`${SINGLE_URL}/s1`); | ||
| expect(req.request.method).toBe('GET'); | ||
| req.flush({ id: 's1' }); | ||
| }); | ||
|
|
||
| it('resolveSchemaById de-duplicates concurrent subscribers into a single request', () => { | ||
| const results: any[] = []; | ||
| service.resolveSchemaById('s1').subscribe((v) => results.push(v)); | ||
| service.resolveSchemaById('s1').subscribe((v) => results.push(v)); | ||
| const req = httpMock.expectOne(`${SINGLE_URL}/s1`); | ||
| req.flush({ id: 's1', document: { x: 1 } }); | ||
| expect(results.length).toBe(2); | ||
| expect(results[0]).toEqual(results[1]); | ||
| }); | ||
|
|
||
| it('serves a cached result without a second request', () => { | ||
| service.resolveSchemaById('s1').subscribe(); | ||
| httpMock.expectOne(`${SINGLE_URL}/s1`).flush({ id: 's1' }); | ||
| let got: any; | ||
| service.resolveSchemaById('s1').subscribe((v) => (got = v)); | ||
| httpMock.expectNone(`${SINGLE_URL}/s1`); | ||
| expect(got).toEqual({ id: 's1' } as any); | ||
| }); | ||
|
|
||
| it('evicts a failed fetch so a later call retries', () => { | ||
| let err: any; | ||
| service.resolveSchemaById('s1').subscribe({ error: (e) => (err = e) }); | ||
| httpMock.expectOne(`${SINGLE_URL}/s1`).flush('boom', { status: 500, statusText: 'Server Error' }); | ||
| expect(err).toBeTruthy(); | ||
| service.resolveSchemaById('s1').subscribe(); | ||
| const retry = httpMock.expectOne(`${SINGLE_URL}/s1`); | ||
| expect(retry.request.method).toBe('GET'); | ||
| retry.flush({ id: 's1' }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { assert } from 'chai'; | ||
| import { RequestVcDocumentBlock } from '../../../dist/policy-engine/blocks/request-vc-document-block.js'; | ||
| import { RequestVcDocumentBlockAddon } from '../../../dist/policy-engine/blocks/request-vc-document-block-addon.js'; | ||
| import { PolicyComponentsUtils } from '../../../dist/policy-engine/policy-components-utils.js'; | ||
|
|
||
| // getData must ship only a lightweight schema reference, not the heavy | ||
| // document + context (the full schema, resolved client-side by id). | ||
|
|
||
| const origGetRef = PolicyComponentsUtils.GetBlockRef; | ||
| const origGetOpts = PolicyComponentsUtils.GetBlockUniqueOptionsObject; | ||
|
|
||
| const fullSchema = () => ({ | ||
| id: 'schema-db-id', | ||
| iri: '#SchemaIri', | ||
| uuid: 'schema-uuid', | ||
| name: 'Project', | ||
| version: '1.0.0', | ||
| document: { $id: '#doc', properties: { big: 'x'.repeat(1000) } }, | ||
| context: { '@context': { big: 'y'.repeat(1000) } }, | ||
| fields: [{ name: 'f' }], | ||
| conditions: [{ if: 1 }], | ||
| }); | ||
|
|
||
| function assertReference(schema) { | ||
| assert.equal(schema.id, 'schema-db-id'); | ||
| assert.equal(schema.iri, '#SchemaIri'); | ||
| assert.equal(schema.uuid, 'schema-uuid'); | ||
| assert.equal(schema.name, 'Project'); | ||
| assert.equal(schema.version, '1.0.0'); | ||
| assert.notProperty(schema, 'document'); | ||
| assert.notProperty(schema, 'context'); | ||
| assert.notProperty(schema, 'fields'); | ||
| assert.notProperty(schema, 'conditions'); | ||
| } | ||
|
|
||
| after(() => { | ||
| PolicyComponentsUtils.GetBlockRef = origGetRef; | ||
| PolicyComponentsUtils.GetBlockUniqueOptionsObject = origGetOpts; | ||
| }); | ||
|
|
||
| describe('RequestVcDocumentBlock runtime — getData schema reference', () => { | ||
| it('emits only light schema identifiers, not document/context', async () => { | ||
| const block = Object.create(RequestVcDocumentBlock.prototype); | ||
| block._schema = fullSchema(); | ||
| block._accessMap = new Map(); | ||
| block.state = {}; | ||
| const ref = { | ||
| uuid: 'blk', | ||
| blockType: 'requestVcDocumentBlock', | ||
| actionType: 'local', | ||
| async getSources() { return []; }, | ||
| }; | ||
| PolicyComponentsUtils.GetBlockRef = () => ref; | ||
| PolicyComponentsUtils.GetBlockUniqueOptionsObject = () => ({}); | ||
|
|
||
| const data = await block.getData({ id: 'u1', location: 'local' }); | ||
| assertReference(data.schema); | ||
| }); | ||
| }); | ||
|
|
||
| describe('RequestVcDocumentBlockAddon runtime — getData schema reference', () => { | ||
| it('emits only light schema identifiers, not document/context', async () => { | ||
| const block = Object.create(RequestVcDocumentBlockAddon.prototype); | ||
| block._schema = fullSchema(); | ||
| block._accessMap = new Map(); | ||
| const ref = { | ||
| uuid: 'blk', | ||
| blockType: 'requestVcDocumentBlockAddon', | ||
| actionType: 'local', | ||
| async getOptions() { return {}; }, | ||
| }; | ||
| PolicyComponentsUtils.GetBlockRef = () => ref; | ||
|
|
||
| const data = await block.getData({ id: 'u1', location: 'local' }); | ||
| assertReference(data.schema); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as the non-addon block: on error this falls back to the reference schema (no
document) and renders an empty form. Useerror: (e) => this._onError(e)so the failure goes through the normal error/loading path.