Browse Source

Load providers on project load

data-source-schema
Artur Arseniev 5 months ago
parent
commit
2ae234c3a5
  1. 13
      packages/core/src/data_sources/config/config.ts
  2. 24
      packages/core/src/data_sources/index.ts
  3. 25
      packages/core/src/data_sources/model/DataSource.ts
  4. 19
      packages/core/src/data_sources/types.ts
  5. 51
      packages/core/test/specs/data_sources/model/DataSource.ts

13
packages/core/src/data_sources/config/config.ts

@ -0,0 +1,13 @@
export interface DataSourcesConfig {
/**
* If true, data source providers will be autoloaded on project load.
* @default false
*/
autoloadProviders?: boolean;
}
const config: () => DataSourcesConfig = () => ({
autoloadProviders: false,
});
export default config;

24
packages/core/src/data_sources/index.ts

@ -26,19 +26,20 @@ import { ItemManagerModule, ModuleConfig } from '../abstract/Module';
import { AddOptions, collectionEvents, ObjectAny, RemoveOptions } from '../common'; import { AddOptions, collectionEvents, ObjectAny, RemoveOptions } from '../common';
import EditorModel from '../editor/model/Editor'; import EditorModel from '../editor/model/Editor';
import { get, set, stringToPath } from '../utils/mixins'; import { get, set, stringToPath } from '../utils/mixins';
import defConfig, { DataSourcesConfig } from './config/config';
import DataRecord from './model/DataRecord'; import DataRecord from './model/DataRecord';
import DataSource from './model/DataSource'; import DataSource from './model/DataSource';
import DataSources from './model/DataSources'; import DataSources from './model/DataSources';
import { DataSourcesEvents, DataSourceProps, DataRecordProps } from './types'; import { DataSourcesEvents, DataSourceProps, DataRecordProps } from './types';
import { Events } from 'backbone'; import { Events } from 'backbone';
export default class DataSourceManager extends ItemManagerModule<ModuleConfig, DataSources> { export default class DataSourceManager extends ItemManagerModule<DataSourcesConfig & ModuleConfig, DataSources> {
storageKey = 'dataSources'; storageKey = 'dataSources';
events = DataSourcesEvents; events = DataSourcesEvents;
destroy(): void {} destroy(): void {}
constructor(em: EditorModel) { constructor(em: EditorModel) {
super(em, 'DataSources', new DataSources([], em), DataSourcesEvents); super(em, 'DataSources', new DataSources([], em), DataSourcesEvents, defConfig());
Object.assign(this, Events); // Mixin Backbone.Events Object.assign(this, Events); // Mixin Backbone.Events
} }
@ -188,7 +189,24 @@ export default class DataSourceManager extends ItemManagerModule<ModuleConfig, D
* @returns {Object} Loaded data sources. * @returns {Object} Loaded data sources.
*/ */
load(data: any) { load(data: any) {
return this.loadProjectData(data); const { config, all, events, em } = this;
const result = this.loadProjectData(data);
if (config.autoloadProviders) {
const dsWithProviders = all.filter((ds) => ds.hasProvider);
if (!!dsWithProviders.length) {
const loadProviders = async () => {
em.trigger(events.providerLoadAllBefore);
const providersToLoad = dsWithProviders.map((ds) => ds.loadProvider());
await Promise.all(providersToLoad);
em.trigger(events.providerLoadAll);
};
loadProviders();
}
}
return result;
} }
postLoad() { postLoad() {

25
packages/core/src/data_sources/model/DataSource.ts

@ -132,6 +132,13 @@ export default class DataSource<DRProps extends DataRecordProps = DataRecordProp
return (this.collection as unknown as DataSources).em; return (this.collection as unknown as DataSources).em;
} }
/**
* Indicates if the data source has a provider for records.
*/
get hasProvider() {
return !!this.attributes.provider;
}
/** /**
* Handles the `add` event for records in the data source. * Handles the `add` event for records in the data source.
* This method triggers a change event on the newly added record. * This method triggers a change event on the newly added record.
@ -222,7 +229,8 @@ export default class DataSource<DRProps extends DataRecordProps = DataRecordProp
} }
async loadProvider() { async loadProvider() {
const { provider } = this.attributes; const { attributes, em } = this;
const { provider } = attributes;
if (!provider) return; if (!provider) return;
@ -235,15 +243,22 @@ export default class DataSource<DRProps extends DataRecordProps = DataRecordProp
const { url, method, headers, body } = providerGet; const { url, method, headers, body } = providerGet;
const fetchProvider = async () => { const fetchProvider = async () => {
const dataSource = this;
try { try {
em.trigger(em.DataSources.events.providerLoadBefore, { dataSource });
const response = await fetch(url, { method, headers, body }); const response = await fetch(url, { method, headers, body });
if (!response.ok) throw new Error(await response.text()); if (!response.ok) throw new Error(await response.text());
const providerResult: DataSourceProviderResult = await response.json(); const result: DataSourceProviderResult = await response.json();
if (result?.records) this.setRecords(result.records as any);
if (result?.schema) this.upSchema(result.schema);
if (providerResult?.records) this.setRecords(providerResult.records as any); em.trigger(em.DataSources.events.providerLoad, { result, dataSource });
if (providerResult?.schema) this.upSchema(providerResult.schema);
} catch (error: any) { } catch (error: any) {
this.em.logError(error.message); em.logError(error.message);
em.trigger(em.DataSources.events.providerLoadError, { dataSource, error });
} }
}; };

19
packages/core/src/data_sources/types.ts

@ -46,6 +46,8 @@ interface BaseDataSource {
* If true will store the data source in the GrapesJS project.json file. * If true will store the data source in the GrapesJS project.json file.
*/ */
skipFromStorage?: boolean; skipFromStorage?: boolean;
[key: string]: unknown;
} }
export enum DataFieldPrimitiveType { export enum DataFieldPrimitiveType {
@ -215,6 +217,23 @@ export enum DataSourcesEvents {
*/ */
pathSource = 'data:pathSource:', pathSource = 'data:pathSource:',
/**
* @event `data:provider:load` Data source provider load.
* @example
* editor.on('data:provider:load', ({ dataSource, result }) => { ... });
*/
providerLoad = 'data:provider:load',
providerLoadBefore = 'data:provider:load:before',
providerLoadError = 'data:provider:load:error',
/**
* @event `data:provider:loadAll` Load of all data source providers (eg. on project load).
* @example
* editor.on('data:provider:loadAll', () => { ... });
*/
providerLoadAll = 'data:provider:loadAll',
providerLoadAllBefore = 'data:provider:loadAll:before',
/** /**
* @event `data` Catch-all event for all the events mentioned above. * @event `data` Catch-all event for all the events mentioned above.
* @example * @example

51
packages/core/test/specs/data_sources/model/DataSource.ts

@ -179,6 +179,7 @@ describe('DataSource', () => {
const addBlogsWithProvider = () => { const addBlogsWithProvider = () => {
return dsm.add({ return dsm.add({
id: 'blogs', id: 'blogs',
name: 'My blogs',
provider: { provider: {
get: getProviderBlogsGet(), get: getProviderBlogsGet(),
}, },
@ -232,8 +233,11 @@ describe('DataSource', () => {
expect(ds.getRecords().length).toBe(0); expect(ds.getRecords().length).toBe(0);
}); });
test('ensure records loaded from the provider are not persisted', async () => { test('records loaded from the provider are not persisted', async () => {
const ds = addBlogsWithProvider(); const ds = addBlogsWithProvider();
const eventLoad = jest.fn();
em.on(dsm.events.providerLoad, eventLoad);
await ds.loadProvider(); await ds.loadProvider();
expect(editor.getProjectData().dataSources).toEqual([ expect(editor.getProjectData().dataSources).toEqual([
@ -241,12 +245,51 @@ describe('DataSource', () => {
{ id: 'users', records: userRecords }, { id: 'users', records: userRecords },
{ {
id: 'blogs', id: 'blogs',
name: 'My blogs',
schema: getMockSchema(), schema: getMockSchema(),
provider: { provider: { get: getProviderBlogsGet() },
get: getProviderBlogsGet(),
},
}, },
]); ]);
expect(eventLoad).toHaveBeenCalledTimes(1);
expect(eventLoad).toHaveBeenCalledWith({
dataSource: ds,
result: getMockProviderResponse(),
});
});
test('load providers on project load', (done) => {
dsm.getConfig().autoloadProviders = true;
editor.on(dsm.events.providerLoadAll, () => {
expect(dsm.get('blogs').getResolvedRecords()).toEqual([
{ ...blogRecords[0], author: userRecords[0] },
{ ...blogRecords[1], author: userRecords[1] },
blogRecords[2],
]);
expect(editor.getProjectData().dataSources).toEqual([
{ id: 'categories', records: categoryRecords },
{ id: 'users', records: userRecords },
{
id: 'blogs',
schema: getMockSchema(),
provider: { get: testApiUrl },
},
]);
done();
});
editor.loadProjectData({
dataSources: [
{ id: 'categories', records: categoryRecords },
{ id: 'users', records: userRecords },
{
id: 'blogs',
provider: { get: testApiUrl },
},
],
});
}); });
}); });
}); });

Loading…
Cancel
Save