mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
feat: register once hook in datasource manager (#4024)
* chore: datasource hook * feat: register once hook in datasource manager * chore: api name * chore: test
This commit is contained in:
parent
1f0acfc2a3
commit
7f936832b9
@ -1,5 +1,6 @@
|
|||||||
import { createMockServer, mockDatabase, supertest } from '@nocobase/test';
|
import { createMockServer, mockDatabase, supertest } from '@nocobase/test';
|
||||||
import { SequelizeDataSource } from '../sequelize-data-source';
|
import { SequelizeDataSource } from '../sequelize-data-source';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
describe('example', () => {
|
describe('example', () => {
|
||||||
test.skip('case1', async () => {
|
test.skip('case1', async () => {
|
||||||
@ -153,4 +154,48 @@ describe('example', () => {
|
|||||||
|
|
||||||
await app.destroy();
|
await app.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should register every datasource instance', async () => {
|
||||||
|
const hook = vi.fn();
|
||||||
|
|
||||||
|
const app = await createMockServer({
|
||||||
|
acl: false,
|
||||||
|
resourcer: {
|
||||||
|
prefix: '/api/',
|
||||||
|
},
|
||||||
|
name: 'update-filter',
|
||||||
|
});
|
||||||
|
|
||||||
|
app.dataSourceManager.afterAddDataSource(hook);
|
||||||
|
// it should be called on main datasource
|
||||||
|
expect(hook).toBeCalledTimes(1);
|
||||||
|
|
||||||
|
const database = mockDatabase({
|
||||||
|
tablePrefix: 'ds1_',
|
||||||
|
});
|
||||||
|
|
||||||
|
// it should be called when adding a new datasource
|
||||||
|
const ds1 = new SequelizeDataSource({
|
||||||
|
name: 'ds1',
|
||||||
|
resourceManager: {},
|
||||||
|
collectionManager: {
|
||||||
|
database,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ds1.collectionManager.defineCollection({
|
||||||
|
name: 'test1',
|
||||||
|
fields: [{ type: 'string', name: 'name' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
await ds1.collectionManager.sync();
|
||||||
|
|
||||||
|
ds1.acl.allow('test1', 'update', 'public');
|
||||||
|
|
||||||
|
await app.dataSourceManager.add(ds1);
|
||||||
|
|
||||||
|
expect(hook).toBeCalledTimes(2);
|
||||||
|
|
||||||
|
await app.destroy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,10 +2,14 @@ import { ToposortOptions } from '@nocobase/utils';
|
|||||||
import { DataSource } from './data-source';
|
import { DataSource } from './data-source';
|
||||||
import { DataSourceFactory } from './data-source-factory';
|
import { DataSourceFactory } from './data-source-factory';
|
||||||
|
|
||||||
|
type DataSourceHook = (dataSource: DataSource) => void;
|
||||||
|
|
||||||
export class DataSourceManager {
|
export class DataSourceManager {
|
||||||
dataSources: Map<string, DataSource>;
|
dataSources: Map<string, DataSource>;
|
||||||
factory: DataSourceFactory = new DataSourceFactory();
|
factory: DataSourceFactory = new DataSourceFactory();
|
||||||
|
|
||||||
|
onceHooks: Array<DataSourceHook> = [];
|
||||||
|
|
||||||
protected middlewares = [];
|
protected middlewares = [];
|
||||||
|
|
||||||
constructor(public options = {}) {
|
constructor(public options = {}) {
|
||||||
@ -16,6 +20,10 @@ export class DataSourceManager {
|
|||||||
async add(dataSource: DataSource, options: any = {}) {
|
async add(dataSource: DataSource, options: any = {}) {
|
||||||
await dataSource.load(options);
|
await dataSource.load(options);
|
||||||
this.dataSources.set(dataSource.name, dataSource);
|
this.dataSources.set(dataSource.name, dataSource);
|
||||||
|
|
||||||
|
for (const hook of this.onceHooks) {
|
||||||
|
hook(dataSource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use(fn: any, options?: ToposortOptions) {
|
use(fn: any, options?: ToposortOptions) {
|
||||||
@ -36,4 +44,15 @@ export class DataSourceManager {
|
|||||||
return ds.middleware(this.middlewares)(ctx, next);
|
return ds.middleware(this.middlewares)(ctx, next);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
afterAddDataSource(hook: DataSourceHook) {
|
||||||
|
this.addHookAndRun(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addHookAndRun(hook: DataSourceHook) {
|
||||||
|
this.onceHooks.push(hook);
|
||||||
|
for (const dataSource of this.dataSources.values()) {
|
||||||
|
hook(dataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,18 +79,23 @@ export abstract class DataSource extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
middleware(middlewares: any = []) {
|
middleware(middlewares: any = []) {
|
||||||
|
const dataSource = this;
|
||||||
|
|
||||||
if (!this['_used']) {
|
if (!this['_used']) {
|
||||||
for (const [fn, options] of middlewares) {
|
for (const [fn, options] of middlewares) {
|
||||||
this.resourceManager.use(fn, options);
|
this.resourceManager.use(fn, options);
|
||||||
}
|
}
|
||||||
this['_used'] = true;
|
this['_used'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
ctx.getCurrentRepository = () => {
|
ctx.getCurrentRepository = () => {
|
||||||
const { resourceName, resourceOf } = ctx.action;
|
const { resourceName, resourceOf } = ctx.action;
|
||||||
return this.collectionManager.getRepository(resourceName, resourceOf);
|
return this.collectionManager.getRepository(resourceName, resourceOf);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ctx.dataSource = dataSource;
|
||||||
|
|
||||||
return compose([this.collectionToResourceMiddleware(), this.resourceManager.middleware()])(ctx, next);
|
return compose([this.collectionToResourceMiddleware(), this.resourceManager.middleware()])(ctx, next);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,11 @@ import { columns2Appends } from '../utils';
|
|||||||
|
|
||||||
export async function exportXlsx(ctx: Context, next: Next) {
|
export async function exportXlsx(ctx: Context, next: Next) {
|
||||||
const { title, filter, sort, fields, except } = ctx.action.params;
|
const { title, filter, sort, fields, except } = ctx.action.params;
|
||||||
const { resourceName, resourceOf } = ctx.action;
|
|
||||||
let columns = ctx.action.params.values?.columns || ctx.action.params?.columns;
|
let columns = ctx.action.params.values?.columns || ctx.action.params?.columns;
|
||||||
if (typeof columns === 'string') {
|
if (typeof columns === 'string') {
|
||||||
columns = JSON.parse(columns);
|
columns = JSON.parse(columns);
|
||||||
}
|
}
|
||||||
const repository = ctx.db.getRepository<any>(resourceName, resourceOf) as Repository;
|
const repository = ctx.getCurrentRepository() as Repository;
|
||||||
const collection = repository.collection;
|
const collection = repository.collection;
|
||||||
columns = columns?.filter((col) => collection.hasField(col.dataIndex[0]) && col?.dataIndex?.length > 0);
|
columns = columns?.filter((col) => collection.hasField(col.dataIndex[0]) && col?.dataIndex?.length > 0);
|
||||||
const appends = columns2Appends(columns, ctx);
|
const appends = columns2Appends(columns, ctx);
|
||||||
|
@ -5,10 +5,17 @@ export class PluginExportServer extends Plugin {
|
|||||||
beforeLoad() {}
|
beforeLoad() {}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.app.resourcer.registerActionHandler('export', exportXlsx);
|
this.app.dataSourceManager.afterAddDataSource((dataSource) => {
|
||||||
this.app.acl.setAvailableAction('export', {
|
// @ts-ignore
|
||||||
displayName: '{{t("Export")}}',
|
if (!dataSource.collectionManager?.db) {
|
||||||
allowConfigureFields: true,
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.resourceManager.registerActionHandler('export', exportXlsx);
|
||||||
|
dataSource.acl.setAvailableAction('export', {
|
||||||
|
displayName: '{{t("Export")}}',
|
||||||
|
allowConfigureFields: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ export function columns2Appends(columns, ctx) {
|
|||||||
const { resourceName } = ctx.action;
|
const { resourceName } = ctx.action;
|
||||||
const appends = new Set([]);
|
const appends = new Set([]);
|
||||||
for (const column of columns) {
|
for (const column of columns) {
|
||||||
let collection = ctx.db.getCollection(resourceName);
|
let collection = ctx.dataSource.collectionManager.getCollection(resourceName);
|
||||||
const appendColumns = [];
|
const appendColumns = [];
|
||||||
for (let i = 0, iLen = column.dataIndex.length; i < iLen; i++) {
|
for (let i = 0, iLen = column.dataIndex.length; i < iLen; i++) {
|
||||||
const field = collection.getField(column.dataIndex[i]);
|
const field = collection.getField(column.dataIndex[i]);
|
||||||
if (field?.target) {
|
if (field?.target) {
|
||||||
appendColumns.push(column.dataIndex[i]);
|
appendColumns.push(column.dataIndex[i]);
|
||||||
collection = ctx.db.getCollection(field.target);
|
collection = ctx.dataSource.collectionManager.getCollection(field.target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (appendColumns.length > 0) {
|
if (appendColumns.length > 0) {
|
||||||
|
@ -11,18 +11,25 @@ export class PluginImportServer extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.app.resourcer.use(importMiddleware);
|
this.app.dataSourceManager.afterAddDataSource((dataSource) => {
|
||||||
this.app.resourcer.registerActionHandler('downloadXlsxTemplate', downloadXlsxTemplate);
|
// @ts-ignore
|
||||||
this.app.resourcer.registerActionHandler('importXlsx', importXlsx);
|
if (!dataSource.collectionManager?.db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.app.acl.setAvailableAction('importXlsx', {
|
dataSource.resourceManager.use(importMiddleware);
|
||||||
displayName: '{{t("Import")}}',
|
dataSource.resourceManager.registerActionHandler('downloadXlsxTemplate', downloadXlsxTemplate);
|
||||||
allowConfigureFields: true,
|
dataSource.resourceManager.registerActionHandler('importXlsx', importXlsx);
|
||||||
type: 'new-data',
|
|
||||||
onNewRecord: true,
|
dataSource.acl.setAvailableAction('importXlsx', {
|
||||||
|
displayName: '{{t("Import")}}',
|
||||||
|
allowConfigureFields: true,
|
||||||
|
type: 'new-data',
|
||||||
|
onNewRecord: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
dataSource.acl.allow('*', 'downloadXlsxTemplate', 'loggedIn');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.acl.allow('*', 'downloadXlsxTemplate', 'loggedIn');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async install(options: InstallOptions) {
|
async install(options: InstallOptions) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user